diff --git a/LICENSE b/LICENSE index 8c5e4fe4c..3193eb8ce 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/README.md b/README.md index 57daebf87..f75590a5b 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 ======================= diff --git a/docs/conf.py b/docs/conf.py index 460886b4d..dd6cc31fb 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -66,7 +66,7 @@ # 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 @@ -74,7 +74,7 @@ # # 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/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index e616adad0..2db1f65f2 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,13 @@ 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``. + 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. @@ -57,124 +59,114 @@ 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 +`in the examples directory `_ +and has a source file and a Makefile fragment with content as descibed above:: -Directory:: + micropython/ + └──examples/ + └──usercmodule/ + └──cexample/ + ├── examplemodule.c + └── micropython.mk - example/ - ├── example.c - └── micropython.mk +Refer to the comments in these 2 files for additional explanation. +Next to the ``cexample`` module there's also ``cppexample`` which +works in the same way but shows one way of mixing C and C++ code +in MicroPython. -``example.c`` - -.. code-block:: c - - // Include required definitions first. - #include "py/obj.h" - #include "py/runtime.h" - #include "py/builtin.h" - - // This is the function which will be called from Python as example.add_ints(a, b). - STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { - // Extract the ints from the micropython input objects - int a = mp_obj_get_int(a_obj); - int b = mp_obj_get_int(b_obj); +Compiling the cmodule into MicroPython +-------------------------------------- - // Calculate the addition and convert to MicroPython object. - return mp_obj_new_int(a + b); - } - // Define a Python reference to the function above - STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); +To build such a module, compile MicroPython (see `getting started +`_), +applying 2 modifications: - // Define all properties of the example module. - // Table entries are key/value pairs of the attribute name (a string) - // and the MicroPython object reference. - // All identifiers and strings are written as MP_QSTR_xxx and will be - // optimized to word-sized integers by the build system (interned strings). - STATIC const mp_rom_map_elem_t example_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) }, - { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, - }; - STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); +- an extra ``make`` flag named ``USER_C_MODULES`` set to the directory + containing all modules you want included (not to the module itself). + For building the example modules which come with MicroPython, + set ``USER_C_MODULES`` to the ``examples/usercmodule`` directory. + For your own projects it's more convenient to keep custom code out of + the main source tree so a typical project directory structure will look + like this:: - // Define module object. - const mp_obj_module_t example_user_cmodule = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t*)&example_module_globals, - }; + my_project/ + ├── modules/ + │ └──example1/ + │ ├──example1.c + │ └──micropython.mk + │ └──example2/ + │ ├──example2.c + │ └──micropython.mk + └── micropython/ + ├──ports/ + ... ├──stm32/ + ... - // Register the module to make it available in Python - MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED); + with ``USER_C_MODULES`` set to the ``my_project/modules`` directory. -``micropython.mk`` +- all modules found in this directory will be compiled, but only those + which are explicitly enabled will be availabe for importing. Enabling a + module is done by setting the preprocessor define from its module + registration to 1. For example if the source code defines the module with -.. code-block:: make + .. code-block:: c - EXAMPLE_MOD_DIR := $(USERMOD_DIR) + MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED); - # Add all C files to SRC_USERMOD. - SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c - # We can add our module folder to include paths if needed - # This is not actually needed in this example. - CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR) + then ``MODULE_CEXAMPLE_ENABLED`` has to be set to 1 to make the module available. + This can be done by adding ``CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1`` to + the ``make`` command, or editing ``mpconfigport.h`` or ``mpconfigboard.h`` + to add -Finally you will need to define ``MODULE_EXAMPLE_ENABLED`` to 1. This -can be done by adding ``CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1`` to -the ``make`` command, or editing ``mpconfigport.h`` or -``mpconfigboard.h`` to add + .. code-block:: c -.. code-block:: c + #define MODULE_CEXAMPLE_ENABLED (1) - #define MODULE_EXAMPLE_ENABLED (1) -Note that the exact method depends on the port as they have different -structures. If not done correctly it will compile but importing will -fail to find the module. + Note that the exact method depends on the port as they have different + structures. If not done correctly it will compile but importing will + fail to find the module. +To sum up, here's how the ``cexample`` module from the ``examples/usercmodule`` +directory can be built for the unix port: -Compiling the cmodule into MicroPython --------------------------------------- +.. code-block:: bash -To build such a module, compile MicroPython (see `getting started -`_) with an -extra ``make`` flag named ``USER_C_MODULES`` set to the directory containing -all modules you want included (not to the module itself). For example: + cd micropython/ports/unix + make USER_C_MODULES=../../examples/usercmodule CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 all +The build output will show the modules found:: -Directory:: + ... + Including User C Module from ../../examples/usercmodule/cexample + Including User C Module from ../../examples/usercmodule/cppexample + ... - my_project/ - ├── modules/ - │ └──example/ - │ ├──example.c - │ └──micropython.mk - └── micropython/ - ├──ports/ - ... ├──stm32/ - ... -Building for stm32 port: +Or for your own project with a directory structure as shown above, +including both modules and building the stm32 port for example: .. code-block:: bash cd my_project/micropython/ports/stm32 - make USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1 all + make USER_C_MODULES=../../../modules \ + CFLAGS_EXTRA="-DMODULE_EXAMPLE1_ENABLED=1 -DMODULE_EXAMPLE2_ENABLED=1" all Module usage in MicroPython --------------------------- -Once built into your copy of MicroPython, the module implemented -in ``example.c`` above can now be accessed in Python just -like any other builtin module, eg +Once built into your copy of MicroPython, the module +can now be accessed in Python just like any other builtin module, e.g. .. code-block:: python - import example - print(example.add_ints(1, 3)) + import cexample + print(cexample.add_ints(1, 3)) # should display 4 diff --git a/docs/develop/compiler.rst b/docs/develop/compiler.rst new file mode 100644 index 000000000..200765749 --- /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 000000000..7fb1ae47a --- /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 000000000..3dd00a579 --- /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 000000000..87de81d76 Binary files /dev/null and b/docs/develop/img/bitmap.png differ diff --git a/docs/develop/img/collision.png b/docs/develop/img/collision.png new file mode 100644 index 000000000..a67ddd613 Binary files /dev/null and b/docs/develop/img/collision.png differ diff --git a/docs/develop/img/linprob.png b/docs/develop/img/linprob.png new file mode 100644 index 000000000..c28818908 Binary files /dev/null and b/docs/develop/img/linprob.png differ diff --git a/docs/develop/index.rst b/docs/develop/index.rst index f1fd0692e..7a6a6be67 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 000000000..bebddcc8a --- /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 000000000..8f899fa1d --- /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 000000000..5b1690cc8 --- /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/natmod.rst b/docs/develop/natmod.rst index dc9f82e77..8ffe49591 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:: @@ -193,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 diff --git a/docs/develop/optimizations.rst b/docs/develop/optimizations.rst new file mode 100644 index 000000000..d972cde66 --- /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 000000000..59dd57000 --- /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 000000000..132c7b136 --- /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 1b3b9f903..cd1fc4786 100644 --- a/docs/develop/qstr.rst +++ b/docs/develop/qstr.rst @@ -1,3 +1,5 @@ +.. _qstr: + MicroPython string interning ============================ @@ -51,12 +53,13 @@ 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 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 000000000..4bdf4dd7a --- /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 diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index d5c222f3a..79e61a10b 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 -------- - -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, I2C +Software I2C (using bit-banging) works on all output-capable pins, and is +accessed via the :ref:`machine.SoftI2C ` class:: - # construct a software I2C bus - i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000) + from machine import Pin, SoftI2C - # construct a hardware I2C bus - i2c = I2C(0) - i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000) + i2c = SoftI2C(scl=Pin(5), sda=Pin(4), freq=100000) - i2c.scan() # scan for slave devices + i2c.scan() # scan for 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 c884d93fc..a478b6658 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 @@ -363,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 ------------- diff --git a/docs/library/btree.rst b/docs/library/btree.rst index de52ef7e7..ba9108521 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. diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index c6777b8a7..f179a31ef 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -269,3 +269,51 @@ Constants esp32.WAKEUP_ANY_HIGH Selects the wake level for pins. + +Non-Volatile Storage +-------------------- + +This class gives access to the Non-Volatile storage managed by ESP-IDF. The NVS is partitioned +into namespaces and each namespace contains typed key-value pairs. The keys are strings and the +values may be various integer types, strings, and binary blobs. The driver currently only +supports 32-bit signed integers and blobs. + +.. warning:: + + Changes to NVS need to be committed to flash by calling the commit method. Failure + to call commit results in changes being lost at the next reset. + +.. class:: NVS(namespace) + + Create an object providing access to a namespace (which is automatically created if not + present). + +.. method:: NVS.set_i32(key, value) + + Sets a 32-bit signed integer value for the specified key. Remember to call *commit*! + +.. method:: NVS.get_i32(key) + + Returns the signed integer value for the specified key. Raises an OSError if the key does not + exist or has a different type. + +.. method:: NVS.set_blob(key, value) + + Sets a binary blob value for the specified key. The value passed in must support the buffer + protocol, e.g. bytes, bytearray, str. (Note that esp-idf distinguishes blobs and strings, this + method always writes a blob even if a string is passed in as value.) + Remember to call *commit*! + +.. method:: NVS.get_blob(key, buffer) + + Reads the value of the blob for the specified key into the buffer, which must be a bytearray. + Returns the actual length read. Raises an OSError if the key does not exist, has a different + type, or if the buffer is too small. + +.. method:: NVS.erase_key(key) + + Erases a key-value pair. + +.. method:: NVS.commit() + + Commits changes made by *set_xxx* methods to flash. diff --git a/docs/library/machine.I2C.rst b/docs/library/machine.I2C.rst index 743554bc7..f9b951564 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,21 +41,33 @@ 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.Pin.rst b/docs/library/machine.Pin.rst index 095240fe1..f8e9e1054 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 --------- diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index d5a4f390b..ae5446ef7 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/machine.SPI.rst b/docs/library/machine.SPI.rst index ea460a7b8..7565241eb 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 ------- diff --git a/docs/library/machine.Signal.rst b/docs/library/machine.Signal.rst index 651c8c849..1e1fcb548 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 diff --git a/docs/library/machine.Timer.rst b/docs/library/machine.Timer.rst index 2d1287f32..9991d3aeb 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 ------- diff --git a/docs/library/machine.rst b/docs/library/machine.rst index b580353d6..18dc6f2af 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. diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index 8078e29e0..649bcda10 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/docs/library/pyb.RTC.rst b/docs/library/pyb.RTC.rst index 4c736597c..c477e7f03 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/docs/library/uasyncio.rst b/docs/library/uasyncio.rst index a81e532d7..1efee56e9 100644 --- a/docs/library/uasyncio.rst +++ b/docs/library/uasyncio.rst @@ -40,6 +40,10 @@ Core functions Returns the corresponding `Task` object. +.. function:: current_task() + + Return the `Task` object associated with the currently running task. + .. function:: run(coro) Create a new task from the given coroutine and run it until it completes. @@ -121,6 +125,9 @@ class Event Set the event. Any tasks waiting on the event will be scheduled to run. + Note: This must be called from within a task. It is not safe to call this + from an IRQ, scheduler callback, or other thread. See `ThreadSafeFlag`. + .. method:: Event.clear() Clear the event. @@ -132,6 +139,29 @@ class Event This is a coroutine. +class ThreadSafeFlag +-------------------- + +.. class:: ThreadSafeFlag() + + Create a new flag which can be used to synchronise a task with code running + outside the asyncio loop, such as other threads, IRQs, or scheduler + callbacks. Flags start in the cleared state. + +.. method:: ThreadSafeFlag.set() + + Set the flag. If there is a task waiting on the event, it will be scheduled + to run. + +.. method:: ThreadSafeFlag.wait() + + Wait for the flag to be set. If the flag is already set then it returns + immediately. + + A flag may only be waited on by a single task at a time. + + This is a coroutine. + class Lock ---------- @@ -184,7 +214,7 @@ TCP stream connections This is a coroutine. .. class:: Stream() - + This represents a TCP stream connection. To minimise code this class implements both a reader and a writer, and both ``StreamReader`` and ``StreamWriter`` alias to this class. diff --git a/docs/library/ubinascii.rst b/docs/library/ubinascii.rst index 192d34514..721b80508 100644 --- a/docs/library/ubinascii.rst +++ b/docs/library/ubinascii.rst @@ -14,13 +14,11 @@ Functions .. function:: hexlify(data, [sep]) - Convert binary data to hexadecimal representation. Returns bytes string. - - .. admonition:: Difference to CPython - :class: attention + Convert the bytes in the *data* object to a hexadecimal representation. + Returns a bytes object. - If additional argument, *sep* is supplied, it is used as a separator - between hexadecimal values. + If the additional argument *sep* is supplied it is used as a separator + between hexadecimal values. .. function:: unhexlify(data) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 03d03583a..3d435a7be 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. 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. @@ -72,13 +73,32 @@ 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. + - ``'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 + false (i.e. allow "Legacy Pairing"). + Event Handling -------------- @@ -88,12 +108,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:: @@ -108,9 +138,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. @@ -169,8 +199,48 @@ 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 + 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 + 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 + 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:: @@ -196,6 +266,31 @@ 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) + _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:: + + _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) + +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 @@ -338,9 +433,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 @@ -364,6 +459,27 @@ 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) + + 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 @@ -483,6 +599,130 @@ 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. + + +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. + +.. 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 ---------- @@ -498,11 +738,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 diff --git a/docs/library/ussl.rst b/docs/library/ussl.rst index ffe146331..14e3f3ad1 100644 --- a/docs/library/ussl.rst +++ b/docs/library/ussl.rst @@ -13,16 +13,23 @@ facilities for network sockets, both client-side and server-side. Functions --------- -.. function:: ussl.wrap_socket(sock, server_side=False, keyfile=None, certfile=None, cert_reqs=CERT_NONE, ca_certs=None) - +.. function:: ussl.wrap_socket(sock, server_side=False, keyfile=None, certfile=None, cert_reqs=CERT_NONE, ca_certs=None, do_handshake=True) Takes a `stream` *sock* (usually usocket.socket instance of ``SOCK_STREAM`` type), and returns an instance of ssl.SSLSocket, which wraps the underlying stream in an SSL context. Returned object has the usual `stream` interface methods like - ``read()``, ``write()``, etc. In MicroPython, the returned object does not expose - socket interface and methods like ``recv()``, ``send()``. In particular, a - server-side SSL socket should be created from a normal socket returned from + ``read()``, ``write()``, etc. + A server-side SSL socket should be created from a normal socket returned from :meth:`~usocket.socket.accept()` on a non-SSL listening server socket. + - *do_handshake* determines whether the handshake is done as part of the ``wrap_socket`` + or whether it is deferred to be done as part of the initial reads or writes + (there is no ``do_handshake`` method as in CPython). + For blocking sockets doing the handshake immediately is standard. For non-blocking + sockets (i.e. when the *sock* passed into ``wrap_socket`` is in non-blocking mode) + the handshake should generally be deferred because otherwise ``wrap_socket`` blocks + until it completes. Note that in AXTLS the handshake can be deferred until the first + read or write but it then blocks until completion. + Depending on the underlying module implementation in a particular :term:`MicroPython port`, some or all keyword arguments above may be not supported. @@ -31,6 +38,11 @@ Functions Some implementations of ``ussl`` module do NOT validate server certificates, which makes an SSL connection established prone to man-in-the-middle attacks. + CPython's ``wrap_socket`` returns an ``SSLSocket`` object which has methods typical + for sockets, such as ``send``, ``recv``, etc. MicroPython's ``wrap_socket`` + returns an object more similar to CPython's ``SSLObject`` which does not have + these socket methods. + Exceptions ---------- diff --git a/docs/library/utime.rst b/docs/library/utime.rst index 8c222ede5..86fd27b3a 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/docs/reference/glossary.rst b/docs/reference/glossary.rst index d63f37229..27a66aa76 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()``, diff --git a/docs/reference/packages.rst b/docs/reference/packages.rst index 2854df23a..e9adfc176 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 diff --git a/docs/reference/pyboard.py.rst b/docs/reference/pyboard.py.rst index 30230eebc..4fedbb7aa 100644 --- a/docs/reference/pyboard.py.rst +++ b/docs/reference/pyboard.py.rst @@ -48,7 +48,9 @@ Running ``pyboard.py --help`` gives the following output: available --follow follow the output after running the scripts [default if no scripts given] - -f, --filesystem perform a filesystem action + -f, --filesystem perform a filesystem action: cp local :device | cp + :device local | cat path | ls [path] | rm path | mkdir + path | rmdir path Running a command on the device ------------------------------- diff --git a/docs/reference/repl.rst b/docs/reference/repl.rst index 06ba91811..55f76ee1a 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. diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index e870d39f5..bb15bd625 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 96dfdeeab..c4162ff21 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/examples/bluetooth/ble_bonding_peripheral.py b/examples/bluetooth/ble_bonding_peripheral.py new file mode 100644 index 000000000..bd7596dbc --- /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("> 24), end="") + print() diff --git a/examples/rp2/pio_uart_tx.py b/examples/rp2/pio_uart_tx.py new file mode 100644 index 000000000..0f8c1260b --- /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 000000000..dd021a9d5 --- /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 000000000..7264edaa2 --- /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/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c new file mode 100644 index 000000000..f608823c9 --- /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 000000000..dbfe3c5cb --- /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 000000000..06809732a --- /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 000000000..ceb588bef --- /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 000000000..d89384a63 --- /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 000000000..e10d965a0 --- /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/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk index 7e5d2f646..17bbb16f8 100644 --- a/extmod/btstack/btstack.mk +++ b/extmod/btstack/btstack.mk @@ -11,6 +11,8 @@ EXTMOD_SRC_C += extmod/btstack/modbluetooth_btstack.c INC += -I$(TOP)/$(BTSTACK_EXTMOD_DIR) CFLAGS_MOD += -DMICROPY_BLUETOOTH_BTSTACK=1 +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING=1 BTSTACK_DIR = $(TOP)/lib/btstack @@ -66,11 +68,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 diff --git a/extmod/btstack/btstack_hci_uart.c b/extmod/btstack/btstack_hci_uart.c index ae18628a9..7205dba06 100644 --- a/extmod/btstack/btstack_hci_uart.c +++ b/extmod/btstack/btstack_hci_uart.c @@ -38,6 +38,11 @@ #include "mpbtstackport.h" +#define HCI_TRACE (0) +#define COL_OFF "\033[0m" +#define COL_GREEN "\033[0;32m" +#define COL_BLUE "\033[0;34m" + // Implements a btstack btstack_uart_block_t on top of the mphciuart.h // interface to an HCI UART provided by the port. @@ -62,8 +67,7 @@ STATIC int btstack_uart_init(const btstack_uart_config_t *uart_config) { send_handler = NULL; // 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. - if (mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID, 0)) { + if (mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID, MICROPY_HW_BLE_UART_BAUDRATE)) { init_success = false; return -1; } @@ -114,6 +118,14 @@ STATIC void btstack_uart_receive_block(uint8_t *buf, uint16_t len) { } STATIC void btstack_uart_send_block(const uint8_t *buf, uint16_t len) { + #if HCI_TRACE + printf(COL_GREEN "< [% 8d] %02x", (int)mp_hal_ticks_ms(), buf[0]); + for (size_t i = 1; i < len; ++i) { + printf(":%02x", buf[i]); + } + printf(COL_OFF "\n"); + #endif + mp_bluetooth_hci_uart_write(buf, len); send_done = true; } @@ -165,6 +177,13 @@ void mp_bluetooth_btstack_hci_uart_process(void) { while (recv_idx < recv_len && (chr = mp_bluetooth_hci_uart_readchar()) >= 0) { recv_buf[recv_idx++] = chr; if (recv_idx == recv_len) { + #if HCI_TRACE + printf(COL_BLUE "> [% 8d] %02x", (int)mp_hal_ticks_ms(), recv_buf[0]); + for (size_t i = 1; i < recv_len; ++i) { + printf(":%02x", recv_buf[i]); + } + printf(COL_OFF "\n"); + #endif recv_idx = 0; recv_len = 0; if (recv_handler) { diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index ae9603586..5f047e325 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) { @@ -75,6 +80,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; @@ -85,7 +91,7 @@ STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uu } return result; } -#endif +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Notes on supporting background ops (e.g. an attempt to gatts_notify while // an existing notification is in progress): @@ -212,7 +218,7 @@ STATIC mp_btstack_pending_op_t *btstack_enqueue_pending_operation(uint16_t op_ty return pending_op; } -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // Cleans up a pending op of the specified type for this conn_handle (and if specified, value_handle). // Used by MP_BLUETOOTH_BTSTACK_PENDING_WRITE and MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE. @@ -276,7 +282,7 @@ STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t chan } } -#if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS +#if MICROPY_BLUETOOTH_USE_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; @@ -294,20 +300,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); @@ -329,13 +349,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 MICROPY_BLUETOOTH_USE_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 + #endif // MICROPY_BLUETOOTH_USE_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) { @@ -344,6 +364,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); @@ -369,6 +418,8 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t uint8_t length = gap_event_advertising_report_get_data_length(packet); const uint8_t *data = gap_event_advertising_report_get_data(packet); mp_bluetooth_gap_on_scan_result(address_type, address, adv_event_type, rssi, data, length); + #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT } 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); @@ -413,30 +464,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); @@ -447,7 +489,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t // Note: Can't "del" the pending_op from IRQ context. Leave it for the GC. } - #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT } else { DEBUG_printf(" --> hci event type: unknown (0x%02x)\n", event_type); } @@ -466,7 +508,7 @@ STATIC btstack_packet_callback_registration_t hci_event_callback_registration = .callback = &btstack_packet_handler_generic }; -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // For when the handler is being used for service discovery. STATIC void btstack_packet_handler_discover_services(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { (void)channel; @@ -501,7 +543,7 @@ STATIC void btstack_packet_handler_write_with_response(uint8_t packet_type, uint (void)size; btstack_packet_handler(packet_type, packet, MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE); } -#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT STATIC btstack_timer_source_t btstack_init_deinit_timeout; @@ -535,12 +577,12 @@ STATIC bool set_public_address(void) { } STATIC void set_random_address(void) { - #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + #if MICROPY_BLUETOOTH_USE_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 + #endif // MICROPY_BLUETOOTH_USE_ZEPHYR_STATIC_ADDRESS { bd_addr_t static_addr; @@ -595,7 +637,7 @@ int mp_bluetooth_init(void) { btstack_memory_init(); - #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + #if MICROPY_BLUETOOTH_USE_ZEPHYR_STATIC_ADDRESS controller_static_addr_available = false; #endif @@ -622,12 +664,12 @@ int mp_bluetooth_init(void) { sm_set_er(dummy_key); sm_set_ir(dummy_key); - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT gatt_client_init(); // We always require explicitly exchanging MTU with ble.gattc_exchange_mtu(). gatt_client_mtu_enable_auto_negotiation(false); - #endif + #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // Register for HCI events. hci_add_event_handler(&hci_event_callback_registration); @@ -679,10 +721,10 @@ int mp_bluetooth_init(void) { set_random_address(); } - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // 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); - #endif + #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT return 0; } @@ -697,10 +739,10 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_gap_advertise_stop(); - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // Remove our registration for notify/indicate. gatt_client_stop_listening_for_characteristic_value_updates(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->notification); - #endif + #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // Set a timer that will forcibly set the state to TIMEOUT, which will stop the loop below. btstack_run_loop_set_timer(&btstack_init_deinit_timeout, BTSTACK_INIT_DEINIT_TIMEOUT_MS); @@ -760,6 +802,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; @@ -846,15 +921,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; @@ -887,7 +962,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). @@ -910,9 +1008,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) { @@ -936,9 +1034,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); @@ -1072,6 +1169,21 @@ 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; +} + +int mp_bluetooth_gap_passkey(uint16_t conn_handle, uint8_t action, mp_int_t passkey) { + DEBUG_printf("mp_bluetooth_gap_passkey: conn_handle=%d action=%d passkey=%d\n", conn_handle, action, (int)passkey); + return MP_EOPNOTSUPP; +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC btstack_timer_source_t scan_duration_timeout; @@ -1122,6 +1234,10 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, return btstack_error_to_errno(gap_connect(btstack_addr, addr_type)); } +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid) { DEBUG_printf("mp_bluetooth_gattc_discover_primary_services\n"); uint8_t err; @@ -1236,6 +1352,35 @@ int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) { return 0; } -#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + +int mp_bluetooth_l2cap_listen(uint16_t psm, uint16_t mtu) { + DEBUG_printf("mp_bluetooth_l2cap_listen: psm=%d, mtu=%d\n", psm, mtu); + return MP_EOPNOTSUPP; +} + +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); + return MP_EOPNOTSUPP; +} + +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); + return MP_EOPNOTSUPP; +} + +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); + return MP_EOPNOTSUPP; +} + +int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf, size_t *len) { + DEBUG_printf("mp_bluetooth_l2cap_recvinto: conn_handle=%d, cid=%d, len=%d\n", conn_handle, cid, (int)*len); + return MP_EOPNOTSUPP; +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake new file mode 100644 index 000000000..6668a2d8a --- /dev/null +++ b/extmod/extmod.cmake @@ -0,0 +1,44 @@ +# CMake fragment for MicroPython extmod component + +set(MICROPY_EXTMOD_DIR "${MICROPY_DIR}/extmod") +set(MICROPY_OOFATFS_DIR "${MICROPY_DIR}/lib/oofatfs") + +set(MICROPY_SOURCE_EXTMOD + ${MICROPY_EXTMOD_DIR}/machine_i2c.c + ${MICROPY_EXTMOD_DIR}/machine_mem.c + ${MICROPY_EXTMOD_DIR}/machine_pulse.c + ${MICROPY_EXTMOD_DIR}/machine_signal.c + ${MICROPY_EXTMOD_DIR}/machine_spi.c + ${MICROPY_EXTMOD_DIR}/modbluetooth.c + ${MICROPY_EXTMOD_DIR}/modbtree.c + ${MICROPY_EXTMOD_DIR}/modframebuf.c + ${MICROPY_EXTMOD_DIR}/moduasyncio.c + ${MICROPY_EXTMOD_DIR}/modubinascii.c + ${MICROPY_EXTMOD_DIR}/moducryptolib.c + ${MICROPY_EXTMOD_DIR}/moductypes.c + ${MICROPY_EXTMOD_DIR}/moduhashlib.c + ${MICROPY_EXTMOD_DIR}/moduheapq.c + ${MICROPY_EXTMOD_DIR}/modujson.c + ${MICROPY_EXTMOD_DIR}/modurandom.c + ${MICROPY_EXTMOD_DIR}/modure.c + ${MICROPY_EXTMOD_DIR}/moduselect.c + ${MICROPY_EXTMOD_DIR}/modussl_axtls.c + ${MICROPY_EXTMOD_DIR}/modussl_mbedtls.c + ${MICROPY_EXTMOD_DIR}/modutimeq.c + ${MICROPY_EXTMOD_DIR}/moduwebsocket.c + ${MICROPY_EXTMOD_DIR}/moduzlib.c + ${MICROPY_EXTMOD_DIR}/modwebrepl.c + ${MICROPY_EXTMOD_DIR}/uos_dupterm.c + ${MICROPY_EXTMOD_DIR}/utime_mphal.c + ${MICROPY_EXTMOD_DIR}/vfs.c + ${MICROPY_EXTMOD_DIR}/vfs_blockdev.c + ${MICROPY_EXTMOD_DIR}/vfs_fat.c + ${MICROPY_EXTMOD_DIR}/vfs_fat_diskio.c + ${MICROPY_EXTMOD_DIR}/vfs_fat_file.c + ${MICROPY_EXTMOD_DIR}/vfs_lfs.c + ${MICROPY_EXTMOD_DIR}/vfs_posix.c + ${MICROPY_EXTMOD_DIR}/vfs_posix_file.c + ${MICROPY_EXTMOD_DIR}/vfs_reader.c + ${MICROPY_EXTMOD_DIR}/virtpin.c + ${MICROPY_EXTMOD_DIR}/nimble/modbluetooth_nimble.c +) diff --git a/extmod/extmod.mk b/extmod/extmod.mk index e312acba8..b000b058d 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 ################################################################################ diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index c804cf570..44161fbbb 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -300,54 +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 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_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 machine_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; +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")); } - - // create new soft I2C object - machine_i2c_obj_t *self = m_new_obj(machine_i2c_obj_t); - self->base.type = &machine_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); + 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); @@ -662,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; @@ -693,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, @@ -700,12 +702,13 @@ 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, + .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 f951c1f21..9e4374732 100644 --- a/extmod/machine_i2c.h +++ b/extmod/machine_i2c.h @@ -27,6 +27,20 @@ #define MICROPY_INCLUDED_EXTMOD_MACHINE_I2C_H #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) @@ -37,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); @@ -56,8 +73,8 @@ 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_dict_t mp_machine_soft_i2c_locals_dict; +extern const mp_obj_type_t mp_machine_soft_i2c_type; +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/extmod/machine_mem.c b/extmod/machine_mem.c index a1494bd51..73e2f7fd1 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/extmod/machine_spi.c b/extmod/machine_spi.c index a7f96573a..c951a5137 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/extmod/machine_spi.h b/extmod/machine_spi.h index db21e1cd3..ca92c719a 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/extmod/modbluetooth.c b/extmod/modbluetooth.c index daf9cd0d1..269493f0a 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" @@ -44,34 +43,43 @@ #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 + +#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 +#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 -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; + #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS bool irq_scheduled; 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 - mp_obj_t irq_read_request_data_tuple; #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) { @@ -90,7 +98,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; @@ -154,7 +162,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; } @@ -207,8 +215,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); @@ -227,7 +234,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, @@ -249,28 +256,23 @@ 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; + #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. - 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); - o->irq_data_uuid.base.type = &bluetooth_uuid_type; + 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 = &mp_type_bluetooth_uuid; // Allocate the default ringbuf. ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); + #endif MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o); } @@ -293,8 +295,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) { @@ -314,8 +314,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: @@ -337,6 +341,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); @@ -350,9 +355,10 @@ 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.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 +368,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 @@ -370,6 +376,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)); @@ -380,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")); } @@ -445,7 +474,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); @@ -458,11 +487,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); @@ -486,7 +515,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")); } @@ -503,7 +532,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]); @@ -518,7 +547,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")); } @@ -653,6 +682,23 @@ 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); + +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 + // ---------------------------------------------------------------------------- // Bluetooth object: GATTS (Peripheral/Advertiser role) // ---------------------------------------------------------------------------- @@ -715,13 +761,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_set_buffer_obj, 3 // Bluetooth object: GATTC (Central/Scanner role) // ---------------------------------------------------------------------------- -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT 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 && 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]); @@ -736,7 +782,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]); @@ -784,7 +830,76 @@ STATIC mp_obj_t bluetooth_ble_gattc_exchange_mtu(mp_obj_t self_in, mp_obj_t conn } 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 +#endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + +#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 + +#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 @@ -802,15 +917,19 @@ 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) }, - // GATT Server (i.e. peripheral/advertiser role) + #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 { 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) }, { MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_indicate), MP_ROM_PTR(&bluetooth_ble_gatts_indicate_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) }, - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - // GATT Client (i.e. central/scanner role) + #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + // GATT Client { MP_ROM_QSTR(MP_QSTR_gattc_discover_services), MP_ROM_PTR(&bluetooth_ble_gattc_discover_services_obj) }, { MP_ROM_QSTR(MP_QSTR_gattc_discover_characteristics), MP_ROM_PTR(&bluetooth_ble_gattc_discover_characteristics_obj) }, { MP_ROM_QSTR(MP_QSTR_gattc_discover_descriptors), MP_ROM_PTR(&bluetooth_ble_gattc_discover_descriptors_obj) }, @@ -818,10 +937,20 @@ 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 + #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); -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, @@ -830,8 +959,10 @@ 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) }, + + // 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) }, @@ -848,23 +979,21 @@ const mp_obj_module_t mp_module_ubluetooth = { // Helpers -#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) { +#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; 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) { 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 +1009,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); } @@ -920,6 +1048,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); @@ -936,6 +1067,8 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_SCAN_DONE) { // No params required. data_tuple->len = 0; + #endif + #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT } else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT) { // conn_handle, start_handle, end_handle, uuid ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, &o->irq_data_uuid, NULL); @@ -954,7 +1087,7 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || event == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { // conn_handle, value_handle, status ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, NULL, NULL); - #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT } MICROPY_PY_BLUETOOTH_EXIT @@ -965,11 +1098,240 @@ 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 mp_int_t *numeric, size_t n_unsigned, size_t n_signed, + const uint8_t *addr, + 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)); + if (o->irq_handler == mp_const_none) { + return mp_const_none; + } + + mp_obj_array_t mv_addr; + 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; + data_tuple->len = 0; + + 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_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) { + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(uuid); + } + #endif + 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); + + 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_NUMERIC NULL +#define NULL_ADDR 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) { + 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) { + 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) { + 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) { + 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; + } + 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) { + 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, 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) { + 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) { + 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) { + 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); + return ret; +} + +void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { + 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_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { + 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); + return ret; +} + +void mp_bluetooth_on_l2cap_connect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { + 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_on_l2cap_disconnect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t status) { + 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_on_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, uint8_t status) { + 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_on_l2cap_recv(uint16_t conn_handle, uint16_t cid) { + 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_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) { + 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); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT +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) { + 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) { + 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) { + 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) { + 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) { + const uint8_t *combined_data; + size_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; + } + + 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); + } +} + +void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { + 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_GATT_CLIENT + +#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. @@ -1031,6 +1393,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)); @@ -1052,6 +1427,23 @@ void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t valu schedule_ringbuf(atomic_state); } +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 MP_BLUETOOTH_GATTS_NO_ERROR; +} + +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 @@ -1083,7 +1475,9 @@ void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uin } schedule_ringbuf(atomic_state); } +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT 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) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); @@ -1130,31 +1524,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); } @@ -1168,49 +1565,13 @@ void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle } schedule_ringbuf(atomic_state); } +#endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT -#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; - } -} -#endif +#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -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); -} +// ---------------------------------------------------------------------------- +// 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); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 2df4c3c25..d126ad6c1 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -43,11 +43,37 @@ #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_ENABLE_GATT_CLIENT +// Enable the client by default if we're enabling central mode. It's possible +// to enable client without central though. +#define MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT (MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE) +#endif + +#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 + +// 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 + +// 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 + +// 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 #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); @@ -63,12 +89,36 @@ // 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) + +// 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) @@ -101,12 +151,41 @@ #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_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_IRQ_PASSKEY_ACTION (31) #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 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 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 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. @@ -133,9 +212,53 @@ _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) +_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) +_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) + +_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) + +_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_NONE = const(0) +_PASSKEY_ACTION_INPUT = const(2) +_PASSKEY_ACTION_DISPLAY = const(3) +_PASSKEY_ACTION_NUMERIC_COMPARISON = const(4) */ -// 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 +270,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): @@ -177,6 +302,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); @@ -192,7 +328,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); @@ -218,6 +354,14 @@ 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); + +// 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 // 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); @@ -227,7 +371,9 @@ int mp_bluetooth_gap_scan_stop(void); // Connect to a found peripheral. int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms); +#endif +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // Find all primary services on the connected peripheral. int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid); @@ -245,7 +391,19 @@ 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_GATT_CLIENT + +#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 + +#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): @@ -253,16 +411,33 @@ int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle); // 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); + +#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); + +// 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. 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 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); @@ -273,7 +448,9 @@ void mp_bluetooth_gap_on_scan_complete(void); // Notify modbluetooth of a scan result. 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); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT // Notify modbluetooth that a service was found (either by discover-all, or discover-by-uuid). 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); @@ -287,15 +464,19 @@ 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); -#endif +#endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS +mp_int_t mp_bluetooth_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu); +void mp_bluetooth_on_l2cap_connect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu); +void mp_bluetooth_on_l2cap_disconnect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t status); +void mp_bluetooth_on_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, uint8_t status); +void mp_bluetooth_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/modframebuf.c b/extmod/modframebuf.c index 2753acc27..18b170078 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); } diff --git a/extmod/modonewire.c b/extmod/modonewire.c index 210bee366..6abe3dfad 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); } diff --git a/extmod/moduasyncio.c b/extmod/moduasyncio.c index 0b15c9e07..fe0f748ca 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; @@ -164,10 +167,16 @@ 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. - if (self->coro == mp_const_none) { + if (TASK_IS_DONE(self)) { return mp_const_false; } // Can't cancel self (not supported yet). @@ -209,6 +218,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 +245,18 @@ 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_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; + } 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 +279,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/modubinascii.c b/extmod/modubinascii.c index 1d4c72b24..9e4f86fbd 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); diff --git a/extmod/moductypes.c b/extmod/moductypes.c index 811258424..ac5a763eb 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -115,8 +115,11 @@ typedef struct _mp_obj_uctypes_struct_t { byte *addr; uint32_t flags; } mp_obj_uctypes_struct_t; - +#if NO_NLR +STATIC mp_obj_t syntax_error(void) { +#else STATIC NORETURN void syntax_error(void) { +#endif mp_raise_TypeError(MP_ERROR_TEXT("syntax error in uctypes descriptor")); } @@ -218,9 +221,12 @@ STATIC mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_ // but scalar structure field is lowered into native Python int, so all // type info is lost. So, we cannot say if it's scalar type description, // or such lowered scalar. - mp_raise_TypeError(MP_ERROR_TEXT("can't unambiguously get sizeof scalar")); + mp_raise_TypeError_or_return(MP_ERROR_TEXT("can't unambiguously get sizeof scalar"), (mp_uint_t)-1); } syntax_error(); +#if NO_NLR + return (mp_uint_t)-1; +#endif } mp_obj_dict_t *d = MP_OBJ_TO_PTR(desc_in); @@ -246,6 +252,9 @@ STATIC mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_ } else { if (!mp_obj_is_type(v, &mp_type_tuple)) { syntax_error(); +#if NO_NLR + return (mp_uint_t)-1; +#endif } mp_obj_tuple_t *t = MP_OBJ_TO_PTR(v); mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(t->items[0]); @@ -288,6 +297,11 @@ STATIC mp_obj_t uctypes_struct_sizeof(size_t n_args, const mp_obj_t *args) { } } mp_uint_t size = uctypes_struct_size(obj_in, layout_type, &max_field_size); +#if NO_NLR + if (size == (mp_uint_t)-1) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_NEW_SMALL_INT(size); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(uctypes_struct_sizeof_obj, 1, 2, uctypes_struct_sizeof); @@ -399,7 +413,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: @@ -477,11 +491,17 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set } if (!mp_obj_is_type(deref, &mp_type_tuple)) { +#if NO_NLR + return +#endif syntax_error(); } if (set_val != MP_OBJ_NULL) { // Cannot assign to aggregate +#if NO_NLR + return +#endif syntax_error(); } @@ -506,6 +526,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 +648,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/moduheapq.c b/extmod/moduheapq.c index 073ce516b..83f4d18a5 100644 --- a/extmod/moduheapq.c +++ b/extmod/moduheapq.c @@ -33,7 +33,7 @@ STATIC mp_obj_list_t *uheapq_get_heap(mp_obj_t heap_in) { if (!mp_obj_is_type(heap_in, &mp_type_list)) { - mp_raise_TypeError(MP_ERROR_TEXT("heap must be a list")); + mp_raise_TypeError_or_return(MP_ERROR_TEXT("heap must be a list"), NULL); } return MP_OBJ_TO_PTR(heap_in); } @@ -72,6 +72,11 @@ STATIC void uheapq_heap_siftup(mp_obj_list_t *heap, mp_uint_t pos) { STATIC mp_obj_t mod_uheapq_heappush(mp_obj_t heap_in, mp_obj_t item) { mp_obj_list_t *heap = uheapq_get_heap(heap_in); +#if NO_NLR + if (heap == NULL) { + return MP_OBJ_NULL; + } +#endif mp_obj_list_append(heap_in, item); uheapq_heap_siftdown(heap, 0, heap->len - 1); return mp_const_none; @@ -80,6 +85,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_uheapq_heappush_obj, mod_uheapq_heappush); STATIC mp_obj_t mod_uheapq_heappop(mp_obj_t heap_in) { mp_obj_list_t *heap = uheapq_get_heap(heap_in); +#if NO_NLR + if (heap == NULL) { + return MP_OBJ_NULL; + } +#endif if (heap->len == 0) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty heap")); } @@ -96,6 +106,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_uheapq_heappop_obj, mod_uheapq_heappop); STATIC mp_obj_t mod_uheapq_heapify(mp_obj_t heap_in) { mp_obj_list_t *heap = uheapq_get_heap(heap_in); +#if NO_NLR + if (heap == NULL) { + return MP_OBJ_NULL; + } +#endif for (mp_uint_t i = heap->len / 2; i > 0;) { uheapq_heap_siftup(heap, --i); } diff --git a/extmod/modujson.c b/extmod/modujson.c index 8dff67358..0ece40dc0 100644 --- a/extmod/modujson.c +++ b/extmod/modujson.c @@ -35,7 +35,13 @@ #if MICROPY_PY_UJSON STATIC mp_obj_t mod_ujson_dump(mp_obj_t obj, mp_obj_t stream) { +#if NO_NLR + if (mp_get_stream_raise(stream, MP_STREAM_OP_WRITE) == NULL) { + return MP_OBJ_NULL; + } +#else mp_get_stream_raise(stream, MP_STREAM_OP_WRITE); +#endif mp_print_t print = {MP_OBJ_TO_PTR(stream), mp_stream_write_adaptor}; mp_obj_print_helper(&print, obj, PRINT_JSON); return mp_const_none; @@ -79,7 +85,12 @@ typedef struct _ujson_stream_t { STATIC byte ujson_stream_next(ujson_stream_t *s) { mp_uint_t ret = s->read(s->stream_obj, &s->cur, 1, &s->errcode); if (s->errcode != 0) { +#if NO_NLR +#pragma message "TODO deal with raising exception" + mp_raise_OSError_or_return(s->errcode, 0xff); +#else mp_raise_OSError(s->errcode); +#endif } if (ret == 0) { s->cur = S_EOF; diff --git a/extmod/modurandom.c b/extmod/modurandom.c index ab83b0f70..5a736c1eb 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/extmod/modure.c b/extmod/modure.c index 328c897d8..3fd6180a9 100644 --- a/extmod/modure.c +++ b/extmod/modure.c @@ -68,7 +68,7 @@ STATIC mp_obj_t match_group(mp_obj_t self_in, mp_obj_t no_in) { mp_obj_match_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t no = mp_obj_get_int(no_in); if (no < 0 || no >= self->num_matches) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, no_in)); + mp_raise_or_return_value(mp_obj_new_exception_arg1(&mp_type_IndexError, no_in), MP_OBJ_NULL); } const char *start = self->caps[no * 2]; @@ -100,14 +100,18 @@ MP_DEFINE_CONST_FUN_OBJ_1(match_groups_obj, match_groups); #if MICROPY_PY_URE_MATCH_SPAN_START_END +#if NO_NLR +STATIC int match_span_helper(size_t n_args, const mp_obj_t *args, mp_obj_t span[2]) { +#else STATIC void match_span_helper(size_t n_args, const mp_obj_t *args, mp_obj_t span[2]) { +#endif mp_obj_match_t *self = MP_OBJ_TO_PTR(args[0]); mp_int_t no = 0; if (n_args == 2) { no = mp_obj_get_int(args[1]); if (no < 0 || no >= self->num_matches) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, args[1])); + mp_raise_or_return_value(mp_obj_new_exception_arg1(&mp_type_IndexError, args[1]), 1); } } @@ -123,25 +127,46 @@ STATIC void match_span_helper(size_t n_args, const mp_obj_t *args, mp_obj_t span span[0] = mp_obj_new_int(s); span[1] = mp_obj_new_int(e); +#if NO_NLR + return 0; +#endif } STATIC mp_obj_t match_span(size_t n_args, const mp_obj_t *args) { mp_obj_t span[2]; +#if NO_NLR + if (match_span_helper(n_args, args, span)) { + return MP_OBJ_NULL; + } +#else match_span_helper(n_args, args, span); +#endif return mp_obj_new_tuple(2, span); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(match_span_obj, 1, 2, match_span); STATIC mp_obj_t match_start(size_t n_args, const mp_obj_t *args) { mp_obj_t span[2]; +#if NO_NLR + if (match_span_helper(n_args, args, span)) { + return MP_OBJ_NULL; + } +#else match_span_helper(n_args, args, span); +#endif return span[0]; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(match_start_obj, 1, 2, match_start); STATIC mp_obj_t match_end(size_t n_args, const mp_obj_t *args) { mp_obj_t span[2]; +#if NO_NLR + if (match_span_helper(n_args, args, span)) { + return MP_OBJ_NULL; + } +#else match_span_helper(n_args, args, span); +#endif return span[1]; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(match_end_obj, 1, 2, match_end); @@ -194,6 +219,12 @@ STATIC mp_obj_t ure_exec(bool is_anchored, uint n_args, const mp_obj_t *args) { // cast is a workaround for a bug in msvc: it treats const char** as a const pointer instead of a pointer to pointer to const char memset((char *)match->caps, 0, caps_num * sizeof(char *)); int res = re1_5_recursiveloopprog(&self->re, &subj, match->caps, caps_num, is_anchored); + #if NO_NLR + if (res == -1) { + // exception + return MP_OBJ_NULL; + } + #endif if (res == 0) { m_del_var(mp_obj_match_t, char *, caps_num, match); return mp_const_none; @@ -236,6 +267,12 @@ STATIC mp_obj_t re_split(size_t n_args, const mp_obj_t *args) { memset((char **)caps, 0, caps_num * sizeof(char *)); int res = re1_5_recursiveloopprog(&self->re, &subj, caps, caps_num, false); + #if NO_NLR + if (res == -1) { + // exception + return MP_OBJ_NULL; + } + #endif // if we didn't have a match, or had an empty match, it's time to stop if (!res || caps[0] == caps[1]) { break; @@ -295,7 +332,12 @@ STATIC mp_obj_t re_sub_helper(size_t n_args, const mp_obj_t *args) { // cast is a workaround for a bug in msvc: it treats const char** as a const pointer instead of a pointer to pointer to const char memset((char *)match->caps, 0, caps_num * sizeof(char *)); int res = re1_5_recursiveloopprog(&self->re, &subj, match->caps, caps_num, false); - + #if NO_NLR + if (res == -1) { + // exception + return MP_OBJ_NULL; + } + #endif // If we didn't have a match, or had an empty match, it's time to stop if (!res || match->caps[0] == match->caps[1]) { break; @@ -334,7 +376,10 @@ STATIC mp_obj_t re_sub_helper(size_t n_args, const mp_obj_t *args) { } if (match_no >= (unsigned int)match->num_matches) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, MP_OBJ_NEW_SMALL_INT(match_no))); + mp_raise_or_return_value( + mp_obj_new_exception_arg1(&mp_type_IndexError, MP_OBJ_NEW_SMALL_INT(match_no)), + MP_OBJ_NULL + ); } const char *start_match = match->caps[match_no * 2]; @@ -343,6 +388,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 @@ -455,7 +503,11 @@ const mp_obj_module_t mp_module_ure = { #if MICROPY_PY_URE_DEBUG #include "re1.5/dumpcode.c" #endif +#if NO_NLR +#include "re1.5/recursiveloop_no_nlr.c" +#else #include "re1.5/recursiveloop.c" +#endif #include "re1.5/charclass.c" #endif // MICROPY_PY_URE diff --git a/extmod/moduselect.c b/extmod/moduselect.c index 80beb8e09..6d8249f42 100644 --- a/extmod/moduselect.c +++ b/extmod/moduselect.c @@ -148,7 +148,7 @@ STATIC mp_obj_t select_select(size_t n_args, const mp_obj_t *args) { // poll the objects mp_uint_t n_ready = poll_map_poll(&poll_map, rwx_len); - if (n_ready > 0 || (timeout != -1 && mp_hal_ticks_ms() - start_tick >= timeout)) { + if (n_ready > 0 || (timeout != (mp_uint_t)-1 && mp_hal_ticks_ms() - start_tick >= timeout)) { // one or more objects are ready, or we had a timeout mp_obj_t list_array[3]; list_array[0] = mp_obj_new_list(rwx_len[0], NULL); @@ -250,7 +250,7 @@ STATIC mp_uint_t poll_poll_internal(uint n_args, const mp_obj_t *args) { for (;;) { // poll the objects n_ready = poll_map_poll(&self->poll_map, NULL); - if (n_ready > 0 || (timeout != -1 && mp_hal_ticks_ms() - start_tick >= timeout)) { + if (n_ready > 0 || (timeout != (mp_uint_t)-1 && mp_hal_ticks_ms() - start_tick >= timeout)) { break; } MICROPY_EVENT_POLL_HOOK diff --git a/extmod/modussl_axtls.c b/extmod/modussl_axtls.c index da5941a55..dbd491ce9 100644 --- a/extmod/modussl_axtls.c +++ b/extmod/modussl_axtls.c @@ -84,7 +84,11 @@ STATIC const char *const ssl_error_tab2[] = { "NOT_SUPPORTED", }; +#if NO_NLR +STATIC mp_obj_t ussl_raise_error(int err) { +#else STATIC NORETURN void ussl_raise_error(int err) { +#endif MP_STATIC_ASSERT(SSL_NOT_OK - 3 == SSL_EAGAIN); MP_STATIC_ASSERT(SSL_ERROR_CONN_LOST - 18 == SSL_ERROR_NOT_SUPPORTED); @@ -111,13 +115,21 @@ STATIC NORETURN void ussl_raise_error(int err) { o_str->len = strlen((char *)o_str->data); o_str->hash = qstr_compute_hash(o_str->data, o_str->len); +#if NO_NLR + mp_raise_OSError(err); +#else // 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)); +#endif } +#if NO_NLR +STATIC mp_obj_t ussl_socket_new(mp_obj_t sock, struct ssl_args *args) { +#else STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args) { +#endif #if MICROPY_PY_USSL_FINALISER mp_obj_ssl_socket_t *o = m_new_obj_with_finaliser(mp_obj_ssl_socket_t); #else @@ -149,6 +161,11 @@ STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args } data = (const byte *)mp_obj_str_get_data(args->cert.u_obj, &len); + #if NO_NLR + if (data == NULL) { + return MP_OBJ_NULL; + } + #endif res = ssl_obj_memory_load(o->ssl_ctx, SSL_OBJ_X509_CERT, data, len, NULL); if (res != SSL_OK) { mp_raise_ValueError(MP_ERROR_TEXT("invalid cert")); @@ -167,16 +184,25 @@ STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args o->ssl_sock = ssl_client_new(o->ssl_ctx, (long)sock, NULL, 0, ext); if (args->do_handshake.u_bool) { - int res = ssl_handshake_status(o->ssl_sock); - - if (res != SSL_OK) { - ussl_raise_error(res); + int r = ssl_handshake_status(o->ssl_sock); + + if (r != SSL_OK) { + if (r == SSL_CLOSE_NOTIFY) { // EOF + r = MP_ENOTCONN; + } else if (r == SSL_EAGAIN) { + r = MP_EAGAIN; + } + ussl_raise_error(r); } } } +#if NO_NLR + return MP_OBJ_FROM_PTR(o); +#else return o; +#endif } STATIC void ussl_socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -242,8 +268,24 @@ STATIC mp_uint_t ussl_socket_write(mp_obj_t o_in, const void *buf, mp_uint_t siz return MP_STREAM_ERROR; } - mp_int_t r = ssl_write(o->ssl_sock, buf, size); + mp_int_t r; +eagain: + r = ssl_write(o->ssl_sock, buf, size); + if (r == 0) { + // see comment in ussl_socket_read above + if (o->blocking) { + goto eagain; + } else { + r = SSL_EAGAIN; + } + } if (r < 0) { + if (r == SSL_CLOSE_NOTIFY || r == SSL_ERROR_CONN_LOST) { + return 0; // EOF + } + if (r == SSL_EAGAIN) { + r = MP_EAGAIN; + } *errcode = r; return MP_STREAM_ERROR; } @@ -321,7 +363,11 @@ STATIC mp_obj_t mod_ssl_wrap_socket(size_t n_args, const mp_obj_t *pos_args, mp_ mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args); +#if NO_NLR + return ussl_socket_new(sock, &args); +#else return MP_OBJ_FROM_PTR(ussl_socket_new(sock, &args)); +#endif } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_ssl_wrap_socket_obj, 1, mod_ssl_wrap_socket); diff --git a/extmod/modussl_mbedtls.c b/extmod/modussl_mbedtls.c index 1677dc6e1..277af37c7 100644 --- a/extmod/modussl_mbedtls.c +++ b/extmod/modussl_mbedtls.c @@ -133,6 +133,7 @@ STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) { } } +// _mbedtls_ssl_recv is called by mbedtls to receive bytes from the underlying socket STATIC int _mbedtls_ssl_recv(void *ctx, byte *buf, size_t len) { mp_obj_t sock = *(mp_obj_t *)ctx; @@ -171,7 +172,7 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) { mbedtls_pk_init(&o->pkey); mbedtls_ctr_drbg_init(&o->ctr_drbg); #ifdef MBEDTLS_DEBUG_C - // Debug level (0-4) + // Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose mbedtls_debug_set_threshold(0); #endif diff --git a/extmod/modutimeq.c b/extmod/modutimeq.c index c7467f3bf..8e3e23f29 100644 --- a/extmod/modutimeq.c +++ b/extmod/modutimeq.c @@ -75,7 +75,12 @@ STATIC bool time_less_than(struct qentry *item, struct qentry *parent) { } STATIC mp_obj_t utimeq_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +#if NO_NLR + if ( mp_arg_check_num(n_args, n_kw, 1, 1, false) ) + return MP_OBJ_NULL; +#else mp_arg_check_num(n_args, n_kw, 1, 1, false); +#endif mp_uint_t alloc = mp_obj_get_int(args[0]); mp_obj_utimeq_t *o = m_new_obj_var(mp_obj_utimeq_t, struct qentry, alloc); o->base.type = type; @@ -85,7 +90,7 @@ STATIC mp_obj_t utimeq_make_new(const mp_obj_type_t *type, size_t n_args, size_t return MP_OBJ_FROM_PTR(o); } -STATIC void utimeq_heap_siftdown(mp_obj_utimeq_t *heap, mp_uint_t start_pos, mp_uint_t pos) { +STATIC void utimeq_heap_shift_down(mp_obj_utimeq_t *heap, mp_uint_t start_pos, mp_uint_t pos) { struct qentry item = heap->items[pos]; while (pos > start_pos) { mp_uint_t parent_pos = (pos - 1) >> 1; @@ -101,7 +106,7 @@ STATIC void utimeq_heap_siftdown(mp_obj_utimeq_t *heap, mp_uint_t start_pos, mp_ heap->items[pos] = item; } -STATIC void utimeq_heap_siftup(mp_obj_utimeq_t *heap, mp_uint_t pos) { +STATIC void utimeq_heap_shift_up(mp_obj_utimeq_t *heap, mp_uint_t pos) { mp_uint_t start_pos = pos; mp_uint_t end_pos = heap->len; struct qentry item = heap->items[pos]; @@ -118,7 +123,7 @@ STATIC void utimeq_heap_siftup(mp_obj_utimeq_t *heap, mp_uint_t pos) { pos = child_pos; } heap->items[pos] = item; - utimeq_heap_siftdown(heap, start_pos, pos); + utimeq_heap_shift_down(heap, start_pos, pos); } STATIC mp_obj_t mod_utimeq_heappush(size_t n_args, const mp_obj_t *args) { @@ -129,11 +134,15 @@ STATIC mp_obj_t mod_utimeq_heappush(size_t n_args, const mp_obj_t *args) { mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("queue overflow")); } mp_uint_t l = heap->len; +#if __EMSCRIPTEN__ + heap->items[l].time = (mp_uint_t)args[1]; +#else heap->items[l].time = MP_OBJ_SMALL_INT_VALUE(args[1]); +#endif heap->items[l].id = utimeq_id++; heap->items[l].callback = args[2]; heap->items[l].args = args[3]; - utimeq_heap_siftdown(heap, 0, heap->len); + utimeq_heap_shift_down(heap, 0, heap->len); heap->len++; return mp_const_none; } @@ -146,11 +155,16 @@ STATIC mp_obj_t mod_utimeq_heappop(mp_obj_t heap_in, mp_obj_t list_ref) { } mp_obj_list_t *ret = MP_OBJ_TO_PTR(list_ref); if (!mp_obj_is_type(list_ref, &mp_type_list) || ret->len < 3) { - mp_raise_TypeError(NULL); + //mp_raise_TypeError(NULL); + mp_raise_msg(&mp_type_TypeError, MP_ERROR_TEXT("invalid arguments")); } struct qentry *item = &heap->items[0]; +#if __EMSCRIPTEN__ + ret->items[0] = (mp_uint_t *)(item->time); +#else ret->items[0] = MP_OBJ_NEW_SMALL_INT(item->time); +#endif ret->items[1] = item->callback; ret->items[2] = item->args; heap->len -= 1; @@ -158,7 +172,7 @@ STATIC mp_obj_t mod_utimeq_heappop(mp_obj_t heap_in, mp_obj_t list_ref) { heap->items[heap->len].callback = MP_OBJ_NULL; // so we don't retain a pointer heap->items[heap->len].args = MP_OBJ_NULL; if (heap->len) { - utimeq_heap_siftup(heap, 0); + utimeq_heap_shift_up(heap, 0); } return mp_const_none; } @@ -171,7 +185,11 @@ STATIC mp_obj_t mod_utimeq_peektime(mp_obj_t heap_in) { } struct qentry *item = &heap->items[0]; +#if __EMSCRIPTEN__ + return (mp_uint_t *)(item->time); +#else return MP_OBJ_NEW_SMALL_INT(item->time); +#endif } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_utimeq_peektime_obj, mod_utimeq_peektime); diff --git a/extmod/moduzlib.c b/extmod/moduzlib.c index ab70d6747..ca6cf5da1 100644 --- a/extmod/moduzlib.c +++ b/extmod/moduzlib.c @@ -58,10 +58,15 @@ STATIC int read_src_stream(TINF_DATA *data) { byte c; mp_uint_t out_sz = stream->read(self->src_stream, &c, 1, &err); if (out_sz == MP_STREAM_ERROR) { - mp_raise_OSError(err); +#if NO_NLR +#pragma message "TODO deal with raising exception" +#endif + mp_raise_OSError_or_return(err,-1); + return -1; } if (out_sz == 0) { - mp_raise_type(&mp_type_EOFError); + mp_raise_type_or_return(&mp_type_EOFError, -1); + return -1; } return c; } @@ -201,7 +206,7 @@ STATIC mp_obj_t mod_uzlib_decompress(size_t n_args, const mp_obj_t *args) { return res; error: - nlr_raise(mp_obj_new_exception_arg1(&mp_type_ValueError, MP_OBJ_NEW_SMALL_INT(st))); + mp_raise_or_return_value(mp_obj_new_exception_arg1(&mp_type_ValueError, MP_OBJ_NEW_SMALL_INT(st)), MP_OBJ_NULL); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_uzlib_decompress_obj, 1, 3, mod_uzlib_decompress); diff --git a/extmod/nimble/hal/hal_uart.c b/extmod/nimble/hal/hal_uart.c index c6d0850fe..cd5e49e30 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", (int)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\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 1ff1c1743..647e8ab47 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 c0d6d64cf..e3a2f872e 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -42,6 +42,17 @@ #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 + +#if MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD || MICROPY_BLUETOOTH_USE_ZEPHYR_STATIC_ADDRESS +// For ble_hs_hci_cmd_tx +#include "nimble/host/src/ble_hs_hci_priv.h" +#endif + #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY NIMBLE" #endif @@ -53,7 +64,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[] = { @@ -67,17 +77,83 @@ 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); + +STATIC ble_uuid_t *create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid, ble_uuid_any_t *storage); +STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in); + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid); +STATIC ble_addr_t create_nimble_addr(uint8_t addr_type, const uint8_t *addr); +#endif + +STATIC void reset_cb(int reason); + +STATIC bool has_public_address(void); +STATIC void set_random_address(bool nrpa); + +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +STATIC int load_irk(void); +#endif + +STATIC void sync_cb(void); + +#if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY +STATIC void ble_hs_shutdown_stop_cb(int status, void *arg); +#endif + +// Successfully registered service/char/desc handles. +STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg); + +// Events about a connected central (we're in peripheral role). +STATIC int central_gap_event_cb(struct ble_gap_event *event, void *arg); +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +// Events about a connected peripheral (we're in central role). +STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg); +#endif +// Used by both of the above. +STATIC int commmon_gap_event_cb(struct ble_gap_event *event, void *arg); + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +// Scan results. +STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg); +#endif + +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT +// Data available (either due to notify/indicate or successful read). +STATIC void gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om); + +// Client discovery callbacks. +STATIC int ble_gattc_service_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg); +STATIC int ble_gattc_characteristic_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *characteristic, void *arg); +STATIC int ble_gattc_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); + +// Client read/write handlers. +STATIC int ble_gattc_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg); +STATIC int ble_gattc_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg); +#endif + +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +// Bonding store. +STATIC int ble_store_ram_read(int obj_type, const union ble_store_key *key, union ble_store_value *value); +STATIC int ble_store_ram_write(int obj_type, const union ble_store_value *val); +STATIC int ble_store_ram_delete(int obj_type, const union ble_store_key *key); +#endif + STATIC int ble_hs_err_to_errno(int err) { DEBUG_printf("ble_hs_err_to_errno: %d\n", err); if (!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; } } @@ -114,6 +190,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; @@ -170,6 +247,14 @@ STATIC void set_random_address(bool nrpa) { // Mark it as STATIC (not RPA or NRPA). addr.val[5] |= 0xc0; } else + #elif MICROPY_BLUETOOTH_USE_ZEPHYR_STATIC_ADDRESS + if (!nrpa) { + DEBUG_printf("set_random_address: Generating static address from Zephyr controller\n"); + uint8_t buf[23]; + rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_VENDOR, 0x09), NULL, 0, buf, sizeof(buf)); + assert(rc == 0); + memcpy(addr.val, buf + 1, 6); + } else #endif { DEBUG_printf("set_random_address: Generating random static address\n"); @@ -182,6 +267,61 @@ 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)) { + // 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); + 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; @@ -192,6 +332,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 { @@ -209,8 +354,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) { @@ -254,8 +397,54 @@ STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { } } -STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { - DEBUG_printf("gap_event_cb: type=%d\n", event->type); +STATIC int commmon_gap_event_cb(struct ble_gap_event *event, void *arg) { + struct ble_gap_conn_desc desc; + + switch (event->type) { + #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + case BLE_GAP_EVENT_NOTIFY_RX: { + uint16_t ev = event->notify_rx.indication == 0 ? MP_BLUETOOTH_IRQ_GATTC_NOTIFY : MP_BLUETOOTH_IRQ_GATTC_INDICATE; + gattc_on_data_available(ev, event->notify_rx.conn_handle, event->notify_rx.attr_handle, event->notify_rx.om); + return 0; + } + #endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + + case BLE_GAP_EVENT_CONN_UPDATE: { + DEBUG_printf("commmon_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); + } + return 0; + } + + case BLE_GAP_EVENT_MTU: { + if (event->mtu.channel_id == BLE_L2CAP_CID_ATT) { + DEBUG_printf("commmon_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); + } + return 0; + } + + case BLE_GAP_EVENT_ENC_CHANGE: { + DEBUG_printf("commmon_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 + return 0; + } + + default: + DEBUG_printf("commmon_gap_event_cb: unknown type %d\n", event->type); + return 0; + } +} + +STATIC int central_gap_event_cb(struct ble_gap_event *event, void *arg) { + DEBUG_printf("central_gap_event_cb: type=%d\n", event->type); if (!mp_bluetooth_is_active()) { return 0; } @@ -264,6 +453,7 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { switch (event->type) { case BLE_GAP_EVENT_CONNECT: + DEBUG_printf("central_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); @@ -273,33 +463,58 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { // Connection failed. mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->connect.conn_handle, 0xff, addr); } - break; + return 0; case BLE_GAP_EVENT_DISCONNECT: // Disconnect. + DEBUG_printf("central_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; + return 0; case BLE_GAP_EVENT_NOTIFY_TX: { - DEBUG_printf("gap_event_cb: notify_tx: %d %d\n", event->notify_tx.indication, event->notify_tx.status); + DEBUG_printf("central_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. 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; + return 0; } - 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); + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + DEBUG_printf("central_gap_event_cb: phy update: %d\n", event->phy_updated.tx_phy); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: { + // We recognized this peer but the peer doesn't recognize us. + DEBUG_printf("central_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); } - break; + + // Allow re-pairing. + return BLE_GAP_REPEAT_PAIRING_RETRY; + } + + case BLE_GAP_EVENT_PASSKEY_ACTION: { + DEBUG_printf("central_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; } } - return 0; + + return commmon_gap_event_cb(event, arg); } #if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY @@ -356,11 +571,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; @@ -368,8 +599,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); @@ -382,22 +611,33 @@ 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. + DEBUG_printf("mp_bluetooth_init: nimble_port_init\n"); 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(); 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(); @@ -413,7 +653,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; } @@ -512,6 +752,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; @@ -558,7 +816,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(nimble_address_mode, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); + ret = ble_gap_adv_start(nimble_address_mode, NULL, BLE_HS_FOREVER, &adv_params, central_gap_event_cb, NULL); if (ret == 0) { return 0; } @@ -581,22 +839,26 @@ 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: - #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK + case BLE_GATT_ACCESS_OP_READ_DSC: { // Allow Python code to override (by using gatts_write), or deny (by returning false) the read. - if (!mp_bluetooth_gatts_on_read_request(conn_handle, value_handle)) { - return BLE_ATT_ERR_READ_NOT_PERMITTED; + // 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. + int req = mp_bluetooth_gatts_on_read_request(conn_handle, value_handle); + if (req) { + return req; } - #endif entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle); if (!entry) { 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: case BLE_GATT_ACCESS_OP_WRITE_DSC: entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle); @@ -611,6 +873,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; @@ -622,6 +886,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); @@ -634,13 +907,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; } @@ -654,7 +925,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; } @@ -666,6 +937,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]; @@ -679,7 +951,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]; @@ -794,20 +1067,41 @@ int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { return 0; } -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#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)); +} -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; - om = SLIST_NEXT(om, om_next); +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; + } } - mp_bluetooth_gattc_on_data_available_end(atomic_state); + + 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 STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) { DEBUG_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1); @@ -877,6 +1171,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); @@ -886,41 +1181,17 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { // Connection failed. mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT, event->connect.conn_handle, 0xff, addr); } - break; + return 0; 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); - - break; - - case BLE_GAP_EVENT_NOTIFY_RX: { - uint16_t ev = event->notify_rx.indication == 0 ? MP_BLUETOOTH_IRQ_GATTC_NOTIFY : MP_BLUETOOTH_IRQ_GATTC_INDICATE; - gattc_on_data_available(ev, event->notify_rx.conn_handle, event->notify_rx.attr_handle, event->notify_rx.om); - break; - } - - case BLE_GAP_EVENT_CONN_UPDATE: - // TODO - break; - - case BLE_GAP_EVENT_CONN_UPDATE_REQ: - // 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; + return 0; } - return 0; + + return commmon_gap_event_cb(event, arg); } int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) { @@ -949,8 +1220,8 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, return ble_hs_err_to_errno(err); } -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_printf("peripheral_discover_service_cb: conn_handle=%d status=%d start_handle=%d\n", conn_handle, error->status, service ? service->start_handle : -1); +STATIC int ble_gattc_service_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) { + DEBUG_printf("ble_gattc_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; } @@ -963,6 +1234,42 @@ STATIC int peripheral_discover_service_cb(uint16_t conn_handle, const struct ble return 0; } +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + +STATIC void gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om) { + // 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); + } + + // 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); +} + int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; @@ -971,15 +1278,15 @@ int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_ if (uuid) { ble_uuid_any_t nimble_uuid; create_nimble_uuid(uuid, &nimble_uuid); - err = ble_gattc_disc_svc_by_uuid(conn_handle, &nimble_uuid.u, &peripheral_discover_service_cb, NULL); + err = ble_gattc_disc_svc_by_uuid(conn_handle, &nimble_uuid.u, &ble_gattc_service_cb, NULL); } else { - err = ble_gattc_disc_all_svcs(conn_handle, &peripheral_discover_service_cb, NULL); + err = ble_gattc_disc_all_svcs(conn_handle, &ble_gattc_service_cb, NULL); } return ble_hs_err_to_errno(err); } -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_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); +STATIC int ble_gattc_characteristic_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *characteristic, void *arg) { + DEBUG_printf("ble_gattc_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; } @@ -1000,15 +1307,15 @@ int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t s if (uuid) { ble_uuid_any_t nimble_uuid; create_nimble_uuid(uuid, &nimble_uuid); - err = ble_gattc_disc_chrs_by_uuid(conn_handle, start_handle, end_handle, &nimble_uuid.u, &ble_gatt_characteristic_cb, NULL); + err = ble_gattc_disc_chrs_by_uuid(conn_handle, start_handle, end_handle, &nimble_uuid.u, &ble_gattc_characteristic_cb, NULL); } else { - err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gatt_characteristic_cb, NULL); + err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gattc_characteristic_cb, NULL); } return ble_hs_err_to_errno(err); } -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_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); +STATIC int ble_gattc_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_printf("ble_gattc_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; } @@ -1025,19 +1332,20 @@ int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - int err = ble_gattc_disc_all_dscs(conn_handle, start_handle, end_handle, &ble_gatt_descriptor_cb, NULL); + int err = ble_gattc_disc_all_dscs(conn_handle, start_handle, end_handle, &ble_gattc_descriptor_cb, NULL); return ble_hs_err_to_errno(err); } -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_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); +STATIC int ble_gattc_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { + uint16_t handle = attr ? attr->handle : (error ? error->att_handle : 0xffff); + DEBUG_printf("ble_gattc_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, handle); if (!mp_bluetooth_is_active()) { return 0; } if (error->status == 0) { gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, attr->handle, attr->om); } - mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_READ_DONE, conn_handle, attr ? attr->handle : -1, error->status); + mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_READ_DONE, conn_handle, handle, error->status); return 0; } @@ -1046,16 +1354,17 @@ int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - int err = ble_gattc_read(conn_handle, value_handle, &ble_gatt_attr_read_cb, NULL); + int err = ble_gattc_read(conn_handle, value_handle, &ble_gattc_attr_read_cb, NULL); return ble_hs_err_to_errno(err); } -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_printf("ble_gatt_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); +STATIC int ble_gattc_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { + uint16_t handle = attr ? attr->handle : (error ? error->att_handle : 0xffff); + DEBUG_printf("ble_gattc_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, handle); if (!mp_bluetooth_is_active()) { return 0; } - mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE, conn_handle, attr->handle, error->status); + mp_bluetooth_gattc_on_read_write_status(MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE, conn_handle, handle, error->status); return 0; } @@ -1068,7 +1377,7 @@ int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const if (mode == MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE) { err = ble_gattc_write_no_rsp_flat(conn_handle, value_handle, value, *value_len); } else if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { - err = ble_gattc_write_flat(conn_handle, value_handle, value, *value_len, &ble_gatt_attr_write_cb, NULL); + err = ble_gattc_write_flat(conn_handle, value_handle, value, *value_len, &ble_gattc_attr_write_cb, NULL); } else { err = BLE_HS_EINVAL; } @@ -1082,6 +1391,518 @@ int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) { 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_ENABLE_GATT_CLIENT + +#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; + bool irq_in_progress; + uint16_t mtu; + os_membuf_t sdu_mem[]; +} mp_bluetooth_nimble_l2cap_channel_t; + +STATIC void destroy_l2cap_channel(); +STATIC int l2cap_channel_event(struct ble_l2cap_event *event, void *arg); +STATIC mp_bluetooth_nimble_l2cap_channel_t *get_l2cap_channel_for_conn_cid(uint16_t conn_handle, uint16_t cid); +STATIC int create_l2cap_channel(uint16_t mtu, mp_bluetooth_nimble_l2cap_channel_t **out); + +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_on_l2cap_connect(event->connect.conn_handle, info.scid, info.psm, info.our_coc_mtu, info.peer_coc_mtu); + } else { + mp_bluetooth_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_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_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); + + // Don't allow granting more credits until after the IRQ is handled. + chan->irq_in_progress = true; + + mp_bluetooth_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: { + 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_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; + 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) { + 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; + + // 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. + // 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 + +#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) { + 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) { + 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/modbluetooth_nimble.h b/extmod/nimble/modbluetooth_nimble.h index 7e401781e..9ed64368b 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.mk b/extmod/nimble/nimble.mk index f8a68bc6f..806630074 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -17,6 +17,18 @@ 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 + +# 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)/, \ @@ -71,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/, \ @@ -83,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 \ @@ -101,7 +113,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 diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index 2ec012940..b68957fab 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -179,63 +179,100 @@ int nimble_sprintf(char *str, const char *fmt, ...) { struct ble_npl_eventq *global_eventq = NULL; +// This must not be called recursively or concurrently with the UART handler. void mp_bluetooth_nimble_os_eventq_run_all(void) { - for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { - int n = 0; - while (evq->head != NULL && mp_bluetooth_nimble_ble_state > MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - struct ble_npl_event *ev = evq->head; - evq->head = ev->next; - if (ev->next) { - ev->next->prev = NULL; - ev->next = NULL; - } - ev->prev = NULL; - DEBUG_EVENT_printf("event_run(%p)\n", ev); - ev->fn(ev); - DEBUG_EVENT_printf("event_run(%p) done\n", ev); - - if (++n > 3) { - // Limit to running 3 tasks per queue. - // Some tasks (such as reset) can enqueue themselves - // making this an infinite loop (while in PENDSV). + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + return; + } + + // Keep running while there are pending events. + while (true) { + struct ble_npl_event *ev = NULL; + + os_sr_t sr; + OS_ENTER_CRITICAL(sr); + // Search all queues for an event. + for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { + ev = evq->head; + if (ev) { + // Remove this event from the queue. + evq->head = ev->next; + if (ev->next) { + ev->next->prev = NULL; + ev->next = NULL; + } + ev->prev = NULL; + + ev->pending = false; + + // Stop searching and execute this event. break; } } + OS_EXIT_CRITICAL(sr); + + if (!ev) { + break; + } + + // Run the event handler. + DEBUG_EVENT_printf("event_run(%p)\n", ev); + ev->fn(ev); + DEBUG_EVENT_printf("event_run(%p) done\n", ev); + + if (ev->pending) { + // If this event has been re-enqueued while it was running, then + // stop running further events. This prevents an infinite loop + // where the reset event re-enqueues itself on HCI timeout. + break; + } } } void ble_npl_eventq_init(struct ble_npl_eventq *evq) { DEBUG_EVENT_printf("ble_npl_eventq_init(%p)\n", evq); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); evq->head = NULL; struct ble_npl_eventq **evq2; for (evq2 = &global_eventq; *evq2 != NULL; evq2 = &(*evq2)->nextq) { } *evq2 = evq; evq->nextq = NULL; + OS_EXIT_CRITICAL(sr); } void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) { DEBUG_EVENT_printf("ble_npl_eventq_put(%p, %p (%p, %p))\n", evq, ev, ev->fn, ev->arg); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); ev->next = NULL; + ev->pending = true; if (evq->head == NULL) { + // Empty list, make this the first item. evq->head = ev; ev->prev = NULL; } else { - struct ble_npl_event *ev2 = evq->head; + // Find the tail of this list. + struct ble_npl_event *tail = evq->head; while (true) { - if (ev2 == ev) { + if (tail == ev) { DEBUG_EVENT_printf(" --> already in queue\n"); - return; + // Already in the list (e.g. a fragmented ACL will enqueue an + // event to process it for each fragment). + break; } - if (ev2->next == NULL) { + if (tail->next == NULL) { + // Found the end of the list, add this event as the tail. + tail->next = ev; + ev->prev = tail; break; } - DEBUG_EVENT_printf(" --> %p\n", ev2->next); - ev2 = ev2->next; + DEBUG_EVENT_printf(" --> %p\n", tail->next); + tail = tail->next; } - ev2->next = ev; - ev->prev = ev2; } + OS_EXIT_CRITICAL(sr); } void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *arg) { @@ -243,6 +280,7 @@ void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *ar ev->fn = fn; ev->arg = arg; ev->next = NULL; + ev->pending = false; } void *ble_npl_event_get_arg(struct ble_npl_event *ev) { @@ -258,44 +296,17 @@ void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) { /******************************************************************************/ // MUTEX -// This is what MICROPY_BEGIN_ATOMIC_SECTION returns on Unix (i.e. we don't -// need to preserve the atomic state to unlock). -#define ATOMIC_STATE_MUTEX_NOT_HELD 0xffffffff - ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) { DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu); mu->locked = 0; - mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; return BLE_NPL_OK; } ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) { - DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u irq=%d\n", mu, (uint)timeout, (uint)mu->locked); + DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u\n", mu, (uint)timeout, (uint)mu->locked); - // This is a recursive mutex which we implement on top of the IRQ priority - // scheme. Unfortunately we have a single piece of global storage, where - // enter/exit critical needs an "atomic state". - - // There are two different acquirers, either running in a VM thread (i.e. - // a direct Python call into NimBLE), or in the NimBLE task (i.e. polling - // or UART RX). - - // On STM32 the NimBLE task runs in PENDSV, so cannot be interrupted by a VM thread. - // Therefore we only need to ensure that a VM thread that acquires a currently-unlocked mutex - // now raises the priority (thus preventing context switches to other VM threads and - // the PENDSV irq). If the mutex is already locked, then it must have been acquired - // by us. - - // On Unix, the critical section is completely recursive and doesn't require us to manage - // state so we just acquire and release every time. - - // TODO: The "volatile" on locked/atomic_state isn't enough to protect against memory re-ordering. - - // First acquirer of this mutex always enters the critical section, unless - // we're on Unix where it happens every time. - if (mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { - mu->atomic_state = mp_bluetooth_nimble_hci_uart_enter_critical(); - } + // All NimBLE code is executed by the scheduler (and is therefore + // implicitly mutexed) so this mutex implementation is a no-op. ++mu->locked; @@ -303,17 +314,11 @@ ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t time } ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { - DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u irq=%d\n", mu, (uint)mu->locked); + DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked); assert(mu->locked > 0); --mu->locked; - // Only exit the critical section for the final release, unless we're on Unix. - if (mu->locked == 0 || mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { - mp_bluetooth_nimble_hci_uart_exit_critical(mu->atomic_state); - mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; - } - return BLE_NPL_OK; } @@ -329,30 +334,19 @@ ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) { ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) { DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count); - // This is called by NimBLE to synchronously wait for an HCI ACK. The - // corresponding ble_npl_sem_release is called directly by the UART rx - // handler (i.e. hal_uart_rx_cb in extmod/nimble/hal/hal_uart.c). - - // So this implementation just polls the UART until either the semaphore - // is released, or the timeout occurs. + // This is only called by NimBLE in ble_hs_hci_cmd_tx to synchronously + // wait for an HCI ACK. The corresponding ble_npl_sem_release is called + // directly by the UART rx handler (i.e. hal_uart_rx_cb in + // extmod/nimble/hal/hal_uart.c). So this loop needs to run only the HCI + // UART processing but not run any events. if (sem->count == 0) { uint32_t t0 = mp_hal_ticks_ms(); while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { - // This can be called either from code running in NimBLE's "task" - // (i.e. PENDSV) or directly by application code, so for the - // latter case, prevent the "task" from running while we poll the - // UART directly. - MICROPY_PY_BLUETOOTH_ENTER - mp_bluetooth_nimble_hci_uart_process(); - MICROPY_PY_BLUETOOTH_EXIT - if (sem->count != 0) { break; } - // Because we're polling, might as well wait for a UART IRQ indicating - // more data available. mp_bluetooth_nimble_hci_uart_wfi(); } @@ -384,6 +378,8 @@ uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { static struct ble_npl_callout *global_callout = NULL; void mp_bluetooth_nimble_os_callout_process(void) { + os_sr_t sr; + OS_ENTER_CRITICAL(sr); uint32_t tnow = mp_hal_ticks_ms(); for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) { if (!c->active) { @@ -393,17 +389,24 @@ void mp_bluetooth_nimble_os_callout_process(void) { DEBUG_CALLOUT_printf("callout_run(%p) tnow=%u ticks=%u evq=%p\n", c, (uint)tnow, (uint)c->ticks, c->evq); c->active = false; if (c->evq) { + // Enqueue this callout for execution in the event queue. ble_npl_eventq_put(c->evq, &c->ev); } else { + // Execute this callout directly. + OS_EXIT_CRITICAL(sr); c->ev.fn(&c->ev); + OS_ENTER_CRITICAL(sr); } DEBUG_CALLOUT_printf("callout_run(%p) done\n", c); } } + OS_EXIT_CRITICAL(sr); } void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, ble_npl_event_fn *ev_cb, void *ev_arg) { DEBUG_CALLOUT_printf("ble_npl_callout_init(%p, %p, %p, %p)\n", c, evq, ev_cb, ev_arg); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); c->active = false; c->ticks = 0; c->evq = evq; @@ -413,17 +416,22 @@ void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, for (c2 = &global_callout; *c2 != NULL; c2 = &(*c2)->nextc) { if (c == *c2) { // callout already in linked list so don't link it in again + OS_EXIT_CRITICAL(sr); return; } } *c2 = c; c->nextc = NULL; + OS_EXIT_CRITICAL(sr); } ble_npl_error_t ble_npl_callout_reset(struct ble_npl_callout *c, ble_npl_time_t ticks) { DEBUG_CALLOUT_printf("ble_npl_callout_reset(%p, %u) tnow=%u\n", c, (uint)ticks, (uint)mp_hal_ticks_ms()); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); c->active = true; c->ticks = ble_npl_time_get() + ticks; + OS_EXIT_CRITICAL(sr); return BLE_NPL_OK; } @@ -493,23 +501,20 @@ void ble_npl_time_delay(ble_npl_time_t ticks) { // CRITICAL // This is used anywhere NimBLE modifies global data structures. -// We need to protect between: -// - A MicroPython VM thread. -// - The NimBLE "task" (e.g. PENDSV on STM32, pthread on Unix). -// On STM32, by disabling PENDSV, we ensure that either: -// - If we're in the NimBLE task, we're exclusive anyway. -// - If we're in a VM thread, we can't be interrupted by the NimBLE task, or switched to another thread. -// On Unix, there's a global mutex. -// TODO: Both ports currently use MICROPY_PY_BLUETOOTH_ENTER in their implementation, -// maybe this doesn't need to be port-specific? +// Currently all NimBLE code is invoked by the scheduler so there should be no +// races, so on STM32 MICROPY_PY_BLUETOOTH_ENTER/MICROPY_PY_BLUETOOTH_EXIT are +// no-ops. However, in the future we may wish to make HCI UART processing +// happen asynchronously (e.g. on RX IRQ), so the port can implement these +// macros accordingly. uint32_t ble_npl_hw_enter_critical(void) { DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); - return mp_bluetooth_nimble_hci_uart_enter_critical(); + MICROPY_PY_BLUETOOTH_ENTER + return atomic_state; } -void ble_npl_hw_exit_critical(uint32_t ctx) { - DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx); - mp_bluetooth_nimble_hci_uart_exit_critical(ctx); +void ble_npl_hw_exit_critical(uint32_t atomic_state) { + MICROPY_PY_BLUETOOTH_EXIT + DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)atomic_state); } diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 3ef07aa9c..d0803f7e2 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -30,18 +30,32 @@ // 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; 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 +75,6 @@ struct ble_npl_callout { struct ble_npl_mutex { volatile uint8_t locked; - volatile uint32_t atomic_state; }; struct ble_npl_sem { @@ -76,7 +89,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/extmod/nimble/syscfg/syscfg.h b/extmod/nimble/syscfg/syscfg.h index 8a6f2338b..a051a8fef 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) @@ -114,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) diff --git a/extmod/re1.5/compilecode.c b/extmod/re1.5/compilecode.c index 3f54b3993..c4d12af87 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 bb337decf..f8cb92629 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/extmod/re1.5/recursiveloop_no_nlr.c b/extmod/re1.5/recursiveloop_no_nlr.c new file mode 100644 index 000000000..da4141cd1 --- /dev/null +++ b/extmod/re1.5/recursiveloop_no_nlr.c @@ -0,0 +1,87 @@ +// Copyright 2007-2009 Russ Cox. All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "re1.5.h" + +static int +recursiveloop(char *pc, const char *sp, Subject *input, const char **subp, int nsubp) +{ + const char *old; + int off; + int ret; + + if (re1_5_stack_chk()) return -1; + + for(;;) { + if(inst_is_consumer(*pc)) { + // If we need to match a character, but there's none left, it's fail + if(sp >= input->end) + return 0; + } + switch(*pc++) { + case Char: + if(*sp != *pc++) + return 0; + case Any: + sp++; + continue; + case Class: + case ClassNot: + if (!_re1_5_classmatch(pc, sp)) + return 0; + pc += *(unsigned char*)pc * 2 + 1; + sp++; + continue; + case NamedClass: + if (!_re1_5_namedclassmatch(pc, sp)) + return 0; + pc++; + sp++; + continue; + case Match: + return 1; + case Jmp: + off = (signed char)*pc++; + pc = pc + off; + continue; + case Split: + off = (signed char)*pc++; + if((ret = recursiveloop(pc, sp, input, subp, nsubp))) + return ret; + pc = pc + off; + continue; + case RSplit: + off = (signed char)*pc++; + if((ret = recursiveloop(pc + off, sp, input, subp, nsubp))) + return ret; + continue; + case Save: + off = (unsigned char)*pc++; + if(off >= nsubp) { + continue; + } + old = subp[off]; + subp[off] = sp; + if((ret = recursiveloop(pc, sp, input, subp, nsubp))) + return ret; + subp[off] = old; + return 0; + case Bol: + if(sp != input->begin) + return 0; + continue; + case Eol: + if(sp != input->end) + return 0; + continue; + } + re1_5_fatal("recursiveloop"); + } +} + +int +re1_5_recursiveloopprog(ByteProg *prog, Subject *input, const char **subp, int nsubp, int is_anchored) +{ + return recursiveloop(HANDLE_ANCHORED(prog->insts, is_anchored), input->begin, input, subp, nsubp); +} diff --git a/extmod/uasyncio/__init__.py b/extmod/uasyncio/__init__.py index 08f924cf2..fa64438f6 100644 --- a/extmod/uasyncio/__init__.py +++ b/extmod/uasyncio/__init__.py @@ -10,6 +10,7 @@ "wait_for_ms": "funcs", "gather": "funcs", "Event": "event", + "ThreadSafeFlag": "event", "Lock": "lock", "open_connection": "stream", "start_server": "stream", diff --git a/extmod/uasyncio/core.py b/extmod/uasyncio/core.py index 045b4cd13..d74763f6a 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 @@ -264,6 +264,10 @@ def get_event_loop(runq_len=0, waitq_len=0): return Loop +def current_task(): + return cur_task + + def new_event_loop(): global _task_queue, _io_queue # TaskQueue of Task instances diff --git a/extmod/uasyncio/event.py b/extmod/uasyncio/event.py index 31cb00e05..c28ad1fb3 100644 --- a/extmod/uasyncio/event.py +++ b/extmod/uasyncio/event.py @@ -14,6 +14,8 @@ def is_set(self): def set(self): # Event becomes set, schedule any tasks waiting on it + # Note: This must not be called from anything except the thread running + # the asyncio loop (i.e. neither hard or soft IRQ, or a different thread). while self.waiting.peek(): core._task_queue.push_head(self.waiting.pop_head()) self.state = True @@ -29,3 +31,32 @@ async def wait(self): core.cur_task.data = self.waiting yield return True + + +# MicroPython-extension: This can be set from outside the asyncio event loop, +# such as other threads, IRQs or scheduler context. Implementation is a stream +# that asyncio will poll until a flag is set. +# Note: Unlike Event, this is self-clearing. +try: + import uio + + class ThreadSafeFlag(uio.IOBase): + def __init__(self): + self._flag = 0 + + def ioctl(self, req, flags): + if req == 3: # MP_STREAM_POLL + return self._flag * flags + return None + + def set(self): + self._flag = 1 + + async def wait(self): + if not self._flag: + yield core._io_queue.queue_read(self) + self._flag = 0 + + +except ImportError: + pass diff --git a/extmod/uasyncio/funcs.py b/extmod/uasyncio/funcs.py index 6e1305c94..93f4fd256 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 None: - core._task_queue.remove(cancel_task) - if cancel_task.coro is None: - # 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/extmod/uasyncio/task.py b/extmod/uasyncio/task.py index 1788cf0ed..68ddf496f 100644 --- a/extmod/uasyncio/task.py +++ b/extmod/uasyncio/task.py @@ -130,13 +130,16 @@ def __init__(self, coro, globals=None): self.ph_rightmost_parent = None # Paring heap def __iter__(self): - if not hasattr(self, "waiting"): + if self.coro is self: + # Signal that the completed-task has been await'ed on. + self.waiting = None + elif not hasattr(self, "waiting"): # Lazily allocated head of linked list of Tasks waiting on completion of this task. self.waiting = TaskQueue() return self def __next__(self): - if not self.coro: + if self.coro is self: # Task finished, raise return value to caller so it can continue. raise self.data else: @@ -145,9 +148,12 @@ def __next__(self): # Set calling task's data to this task that it waits on, to double-link it. core.cur_task.data = self + def done(self): + return self.coro is self + def cancel(self): # Check if task is already finished. - if self.coro is None: + if self.coro is self: return False # Can't cancel self (not supported yet). if self is core.cur_task: @@ -166,3 +172,13 @@ def cancel(self): core._task_queue.push_head(self) self.data = core.CancelledError return True + + def throw(self, value): + # This task raised an exception which was uncaught; handle that now. + # Set the data because it was cleared by the main scheduling loop. + self.data = value + if not hasattr(self, "waiting"): + # Nothing await'ed on the task so call the exception handler. + core._exc_context["exception"] = value + core._exc_context["future"] = self + core.Loop.call_exception_handler(core._exc_context) diff --git a/extmod/uos_dupterm.c b/extmod/uos_dupterm.c index ad706d524..675cb0d93 100644 --- a/extmod/uos_dupterm.c +++ b/extmod/uos_dupterm.c @@ -93,6 +93,9 @@ uintptr_t mp_uos_dupterm_poll(uintptr_t poll_flags) { } int mp_uos_dupterm_rx_chr(void) { +#if defined(__WASI__) + #pragma message "error: incompatible pointer to integer conversion returning 'mp_obj_t' (aka 'void *') from a function with result type 'int' [-Werror,-Wint-conversion]" +#else for (size_t idx = 0; idx < MICROPY_PY_OS_DUPTERM; ++idx) { if (MP_STATE_VM(dupterm_objs[idx]) == MP_OBJ_NULL) { continue; @@ -142,7 +145,7 @@ int mp_uos_dupterm_rx_chr(void) { mp_uos_deactivate(idx, "dupterm: Exception in read() method, deactivating: ", MP_OBJ_FROM_PTR(nlr.ret_val)); } } - +#endif // No chars available return -1; } diff --git a/extmod/utime_mphal.c b/extmod/utime_mphal.c index 6aff2cac7..d053cf128 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 88a9ed4d3..57fc34883 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/extmod/vfs.c b/extmod/vfs.c index 3cb7af1b4..04f6bd68e 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -137,12 +137,19 @@ mp_import_stat_t mp_vfs_import_stat(const char *path) { // delegate to vfs.stat() method mp_obj_t path_o = mp_obj_new_str(path_out, strlen(path_out)); +#if NO_NLR + mp_obj_t stat = mp_vfs_proxy_call(vfs, MP_QSTR_stat, 1, &path_o); + if (stat == MP_OBJ_NULL) { + // assume an exception means that the path is not found + MP_STATE_THREAD(active_exception) = NULL; +#else mp_obj_t stat; nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { stat = mp_vfs_proxy_call(vfs, MP_QSTR_stat, 1, &path_o); nlr_pop(); } else { +#endif // assume an exception means that the path is not found return MP_IMPORT_STAT_NO_EXIST; } @@ -158,29 +165,35 @@ mp_import_stat_t mp_vfs_import_stat(const char *path) { STATIC mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 +#if NO_NLR +#error LFS1/2 unsupported +#endif 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; - } - #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; + 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 nlr_pop(); } else { // Ignore exception (eg block device doesn't support extended readblocks) @@ -191,7 +204,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) { diff --git a/extmod/vfs_lfs.c b/extmod/vfs_lfs.c index 9cf3eb110..dd78269a4 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/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index 35d5f03c5..f865e4606 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/extmod/vfs_posix_file.c b/extmod/vfs_posix_file.c index 264764b4e..ef0bd117e 100644 --- a/extmod/vfs_posix_file.c +++ b/extmod/vfs_posix_file.c @@ -47,7 +47,11 @@ typedef struct _mp_obj_vfs_posix_file_t { #ifdef MICROPY_CPYTHON_COMPAT STATIC void check_fd_is_open(const mp_obj_vfs_posix_file_t *o) { if (o->fd < 0) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("I/O operation on closed file"))); +#else mp_raise_ValueError(MP_ERROR_TEXT("I/O operation on closed file")); +#endif } } #else @@ -163,7 +167,11 @@ STATIC mp_uint_t vfs_posix_file_write(mp_obj_t o_in, const void *buf, mp_uint_t STATIC mp_uint_t vfs_posix_file_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { mp_obj_vfs_posix_file_t *o = MP_OBJ_TO_PTR(o_in); - check_fd_is_open(o); + + if (request != MP_STREAM_CLOSE) { + check_fd_is_open(o); + } + switch (request) { case MP_STREAM_FLUSH: { int ret; diff --git a/lib/embed/abort_.c b/lib/embed/abort_.c index 3051eae81..9e6fccf8f 100644 --- a/lib/embed/abort_.c +++ b/lib/embed/abort_.c @@ -3,5 +3,10 @@ NORETURN void abort_(void); NORETURN void abort_(void) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called"))); + for(;;){} +#else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called")); +#endif } diff --git a/lib/littlefs/README.md b/lib/littlefs/README.md index 1f0aacbf9..ca21a05f4 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 6bae806ce..1f8c9d2d9 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,48 @@ 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) { + if (*p == (struct lfs2_mlist*)node) { + return true; + } + } + + 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) { + 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 +476,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 +513,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 +569,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 +709,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 +737,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 +833,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 +941,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 +1273,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 +1288,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 +1335,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 +1381,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 +1400,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 +1437,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 +1462,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 +1481,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 +1502,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 +1537,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 +1547,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 +1628,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 +1818,9 @@ static int lfs2_dir_compact(lfs2_t *lfs2, return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount) { // check for any inline files that aren't RAM backed and @@ -1903,15 +2008,15 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, return 0; } +#endif /// Top level directory operations /// -int lfs2_mkdir(lfs2_t *lfs2, const char *path) { - LFS2_TRACE("lfs2_mkdir(%p, \"%s\")", (void*)lfs2, path); +#ifndef LFS2_READONLY +static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { - LFS2_TRACE("lfs2_mkdir -> %d", err); return err; } @@ -1920,14 +2025,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 +2039,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 +2047,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 +2057,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 +2079,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 +2096,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 +2123,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 +2131,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 +2142,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 +2162,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 +2186,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 +2196,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 +2221,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 +2233,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 +2302,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 +2400,7 @@ static int lfs2_ctz_extend(lfs2_t *lfs2, lfs2_cache_drop(lfs2, pcache); } } +#endif static int lfs2_ctz_traverse(lfs2_t *lfs2, const lfs2_cache_t *pcache, lfs2_cache_t *rcache, @@ -2380,27 +2447,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 +2479,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 +2501,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 +2513,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 +2536,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 +2549,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 +2559,7 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, file->flags |= LFS2_F_DIRTY; } +#endif } // allocate buffer if needed @@ -2523,54 +2599,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 +2705,9 @@ static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { lfs2_cache_drop(lfs2, &lfs2->pcache); } } +#endif +#ifndef LFS2_READONLY static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file) { file->off = file->pos; lfs2_alloc_ack(lfs2); @@ -2650,10 +2719,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 +2738,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 +2748,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 +2799,22 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { return 0; } +#endif -int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_TRACE("lfs2_file_sync(%p, %p)", (void*)lfs2, (void*)file); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - +#ifndef LFS2_READONLY +static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file) { if (file->flags & LFS2_F_ERRED) { // it's not safe to do anything if our file errored - LFS2_TRACE("lfs2_file_sync -> %d", 0); return 0; } int err = lfs2_file_flush(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_sync -> %d", err); return err; } + if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { // update dir entry @@ -2777,39 +2844,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 +2888,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 +2907,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 +2914,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 +2924,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 +2939,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 +2949,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 +2958,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 +2973,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 +2989,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 +3003,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 +3023,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 +3031,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, err = lfs2_file_relocate(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } @@ -2995,22 +3044,19 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, } file->flags &= ~LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %"PRId32, size); return size; } +#endif -lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t off, int whence) { - LFS2_TRACE("lfs2_file_seek(%p, %p, %"PRId32", %d)", - (void*)lfs2, (void*)file, off, whence); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - +#ifndef LFS2_READONLY // write out everything beforehand, may be noop if rdonly int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_seek -> %d", err); return err; } +#endif // find new pos lfs2_off_t npos = file->pos; @@ -3024,34 +3070,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 +3100,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 +3109,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 +3194,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 +3219,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 +3232,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 +3257,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 +3266,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 +3279,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 +3289,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 +3299,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 +3306,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 +3330,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 +3347,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 +3361,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 +3388,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 +3398,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 +3429,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 +3574,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 +3610,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 +3635,14 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { cleanup: lfs2_deinit(lfs2); - LFS2_TRACE("lfs2_format -> %d", err); return err; + } +#endif -int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { - LFS2_TRACE("lfs2_mount(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs2, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); +static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = lfs2_init(lfs2, cfg); if (err) { - LFS2_TRACE("lfs2_mount -> %d", err); return err; } @@ -3797,28 +3755,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 +3843,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 +3866,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 +3897,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 +3925,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 +3954,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 +4007,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 +4041,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 +4051,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 +4070,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 +4095,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 +4178,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 +4194,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 +4203,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 +4634,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 +4854,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 c89af79ca..e2afc7792 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 diff --git a/lib/mp-readline/readline.c b/lib/mp-readline/readline.c index 296c8aa4a..adbf82aa8 100644 --- a/lib/mp-readline/readline.c +++ b/lib/mp-readline/readline.c @@ -517,6 +517,35 @@ int readline(vstr_t *line, const char *prompt) { } } +#if MICROPY_UNIXSCHEDULE +#include +fd_set rfds; +struct timeval tv; + +int readline_select(vstr_t *line, const char *prompt, int resuming) { + if (resuming== -3) + readline_init(line, prompt); + + for (;;) { + // On Linux, select() modifies timeout to reflect the amount of time not slept + tv.tv_sec = 0; + tv.tv_usec = 16; + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + int rv=select(STDIN_FILENO+1, &rfds, NULL, NULL, &tv); + // 0-1 == -1 =>tmout -1-1 == -2=>failure + if (rv <=0) + return rv-1; + + int c = mp_hal_stdin_rx_chr(); + int r = readline_process_char(c); + if (r >= 0) { + return r; + } + } +} +#endif + void readline_push_history(const char *line) { if (line[0] != '\0' && (MP_STATE_PORT(readline_hist)[0] == NULL diff --git a/lib/mp-readline/readline.h b/lib/mp-readline/readline.h index a19e1209a..411e42020 100644 --- a/lib/mp-readline/readline.h +++ b/lib/mp-readline/readline.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_LIB_MP_READLINE_READLINE_H #define MICROPY_INCLUDED_LIB_MP_READLINE_READLINE_H +#include "py/misc.h" // for vstr_t + #define CHAR_CTRL_A (1) #define CHAR_CTRL_B (2) #define CHAR_CTRL_C (3) diff --git a/lib/oofatfs/ff.c b/lib/oofatfs/ff.c index 0c9d04fe7..34c7eccc3 100644 --- a/lib/oofatfs/ff.c +++ b/lib/oofatfs/ff.c @@ -98,16 +98,16 @@ #define BS_JmpBoot 0 /* x86 jump instruction (3-byte) */ #define BS_OEMName 3 /* OEM name (8-byte) */ -#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD) */ +#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD16) */ #define BPB_SecPerClus 13 /* Cluster size [sector] (BYTE) */ -#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD) */ +#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD16) */ #define BPB_NumFATs 16 /* Number of FATs (BYTE) */ -#define BPB_RootEntCnt 17 /* Size of root directory area for FAT [entry] (WORD) */ -#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD) */ +#define BPB_RootEntCnt 17 /* Size of root directory area for FAT [entry] (WORD16) */ +#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD16) */ #define BPB_Media 21 /* Media descriptor byte (BYTE) */ -#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD) */ -#define BPB_SecPerTrk 24 /* Number of sectors per track for int13h [sector] (WORD) */ -#define BPB_NumHeads 26 /* Number of heads for int13h (WORD) */ +#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD16) */ +#define BPB_SecPerTrk 24 /* Number of sectors per track for int13h [sector] (WORD16) */ +#define BPB_NumHeads 26 /* Number of heads for int13h (WORD16) */ #define BPB_HiddSec 28 /* Volume offset from top of the drive (DWORD) */ #define BPB_TotSec32 32 /* Volume size (32-bit) [sector] (DWORD) */ #define BS_DrvNum 36 /* Physical drive number for int13h (BYTE) */ @@ -117,14 +117,14 @@ #define BS_VolLab 43 /* Volume label string (8-byte) */ #define BS_FilSysType 54 /* Filesystem type string (8-byte) */ #define BS_BootCode 62 /* Boot code (448-byte) */ -#define BS_55AA 510 /* Signature word (WORD) */ +#define BS_55AA 510 /* Signature WORD16 (WORD16) */ #define BPB_FATSz32 36 /* FAT32: FAT size [sector] (DWORD) */ -#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD) */ -#define BPB_FSVer32 42 /* FAT32: Filesystem version (WORD) */ +#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD16) */ +#define BPB_FSVer32 42 /* FAT32: Filesystem version (WORD16) */ #define BPB_RootClus32 44 /* FAT32: Root directory cluster (DWORD) */ -#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD) */ -#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD) */ +#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD16) */ +#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD16) */ #define BS_DrvNum32 64 /* FAT32: Physical drive number for int13h (BYTE) */ #define BS_NTres32 65 /* FAT32: Error flag (BYTE) */ #define BS_BootSig32 66 /* FAT32: Extended boot signature (BYTE) */ @@ -142,8 +142,8 @@ #define BPB_NumClusEx 92 /* exFAT: Number of clusters (DWORD) */ #define BPB_RootClusEx 96 /* exFAT: Root directory start cluster (DWORD) */ #define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */ -#define BPB_FSVerEx 104 /* exFAT: Filesystem version (WORD) */ -#define BPB_VolFlagEx 106 /* exFAT: Volume flags (WORD) */ +#define BPB_FSVerEx 104 /* exFAT: Filesystem version (WORD16) */ +#define BPB_VolFlagEx 106 /* exFAT: Volume flags (WORD16) */ #define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in unit of byte (BYTE) */ #define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in unit of sector (BYTE) */ #define BPB_NumFATsEx 110 /* exFAT: Number of FATs (BYTE) */ @@ -157,23 +157,23 @@ #define DIR_NTres 12 /* Lower case flag (BYTE) */ #define DIR_CrtTime10 13 /* Created time sub-second (BYTE) */ #define DIR_CrtTime 14 /* Created time (DWORD) */ -#define DIR_LstAccDate 18 /* Last accessed date (WORD) */ -#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD) */ +#define DIR_LstAccDate 18 /* Last accessed date (WORD16) */ +#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD16) */ #define DIR_ModTime 22 /* Modified time (DWORD) */ -#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD) */ +#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD16) */ #define DIR_FileSize 28 /* File size (DWORD) */ #define LDIR_Ord 0 /* LFN: LFN order and LLE flag (BYTE) */ #define LDIR_Attr 11 /* LFN: LFN attribute (BYTE) */ #define LDIR_Type 12 /* LFN: Entry type (BYTE) */ #define LDIR_Chksum 13 /* LFN: Checksum of the SFN (BYTE) */ -#define LDIR_FstClusLO 26 /* LFN: MBZ field (WORD) */ +#define LDIR_FstClusLO 26 /* LFN: MBZ field (WORD16) */ #define XDIR_Type 0 /* exFAT: Type of exFAT directory entry (BYTE) */ #define XDIR_NumLabel 1 /* exFAT: Number of volume label characters (BYTE) */ -#define XDIR_Label 2 /* exFAT: Volume label (11-WORD) */ +#define XDIR_Label 2 /* exFAT: Volume label (11-WORD16) */ #define XDIR_CaseSum 4 /* exFAT: Sum of case conversion table (DWORD) */ #define XDIR_NumSec 1 /* exFAT: Number of secondary entries (BYTE) */ -#define XDIR_SetSum 2 /* exFAT: Sum of the set of directory entries (WORD) */ -#define XDIR_Attr 4 /* exFAT: File attribute (WORD) */ +#define XDIR_SetSum 2 /* exFAT: Sum of the set of directory entries (WORD16) */ +#define XDIR_Attr 4 /* exFAT: File attribute (WORD16) */ #define XDIR_CrtTime 8 /* exFAT: Created time (DWORD) */ #define XDIR_ModTime 12 /* exFAT: Modified time (DWORD) */ #define XDIR_AccTime 16 /* exFAT: Last accessed time (DWORD) */ @@ -184,7 +184,7 @@ #define XDIR_AccTZ 24 /* exFAT: Last accessed timezone (BYTE) */ #define XDIR_GenFlags 33 /* exFAT: General secondary flags (BYTE) */ #define XDIR_NumName 35 /* exFAT: Number of file name characters (BYTE) */ -#define XDIR_NameHash 36 /* exFAT: Hash of file name (WORD) */ +#define XDIR_NameHash 36 /* exFAT: Hash of file name (WORD16) */ #define XDIR_ValidFileSize 40 /* exFAT: Valid file size (QWORD) */ #define XDIR_FstClus 52 /* exFAT: First cluster of the file data (DWORD) */ #define XDIR_FileSize 56 /* exFAT: File/Directory size (QWORD) */ @@ -267,7 +267,7 @@ typedef struct { FATFS *fs; /* Object ID 1, volume (NULL:blank entry) */ DWORD clu; /* Object ID 2, containing directory (0:root) */ DWORD ofs; /* Object ID 3, offset in the directory */ - WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */ + WORD16 ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */ } FILESEM; #endif @@ -441,7 +441,7 @@ typedef struct { #if FF_VOLUMES < 1 || FF_VOLUMES > 10 #error Wrong FF_VOLUMES setting #endif -static WORD Fsid; /* Filesystem mount ID */ +static WORD16 Fsid; /* Filesystem mount ID */ #if FF_FS_LOCK != 0 static FILESEM Files[FF_FS_LOCK]; /* Open object lock semaphores */ @@ -529,7 +529,7 @@ static WCHAR LfnBuf[FF_MAX_LFN + 1]; /* LFN working buffer */ #if FF_CODE_PAGE == 0 /* Run-time code page configuration */ #define CODEPAGE CodePage -static WORD CodePage; /* Current code page */ +static WORD16 CodePage; /* Current code page */ static const BYTE *ExCvt, *DbcTbl; /* Pointer to current SBCS up-case table and DBCS code range table below */ static const BYTE Ct437[] = TBL_CT437; @@ -575,19 +575,19 @@ static const BYTE DbcTbl[] = MKCVTBL(TBL_DC, FF_CODE_PAGE); /*-----------------------------------------------------------------------*/ -/* Load/Store multi-byte word in the FAT structure */ +/* Load/Store multi-byte WORD16 in the FAT structure */ /*-----------------------------------------------------------------------*/ -static WORD ld_word (const BYTE* ptr) /* Load a 2-byte little-endian word */ +static WORD16 ld_WORD16 (const BYTE* ptr) /* Load a 2-byte little-endian WORD16 */ { - WORD rv; + WORD16 rv; rv = ptr[1]; rv = rv << 8 | ptr[0]; return rv; } -static DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */ +static DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian WORD16 */ { DWORD rv; @@ -599,7 +599,7 @@ static DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */ } #if FF_FS_EXFAT -static QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */ +static QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian WORD16 */ { QWORD rv; @@ -616,13 +616,13 @@ static QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */ #endif #if !FF_FS_READONLY -static void st_word (BYTE* ptr, WORD val) /* Store a 2-byte word in little-endian */ +static void st_WORD16 (BYTE* ptr, WORD16 val) /* Store a 2-byte WORD16 in little-endian */ { *ptr++ = (BYTE)val; val >>= 8; *ptr++ = (BYTE)val; } -static void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-endian */ +static void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte WORD16 in little-endian */ { *ptr++ = (BYTE)val; val >>= 8; *ptr++ = (BYTE)val; val >>= 8; @@ -631,7 +631,7 @@ static void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-end } #if FF_FS_EXFAT -static void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte word in little-endian */ +static void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte WORD16 in little-endian */ { *ptr++ = (BYTE)val; val >>= 8; *ptr++ = (BYTE)val; val >>= 8; @@ -972,7 +972,7 @@ static FRESULT dec_lock ( /* Decrement object open counter */ UINT i /* Semaphore index (1..) */ ) { - WORD n; + WORD16 n; FRESULT res; @@ -1074,7 +1074,7 @@ static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */ if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */ /* Create FSInfo structure */ mem_set(fs->win, 0, sizeof fs->win); - st_word(fs->win + BS_55AA, 0xAA55); + st_WORD16(fs->win + BS_55AA, 0xAA55); st_dword(fs->win + FSI_LeadSig, 0x41615252); st_dword(fs->win + FSI_StrucSig, 0x61417272); st_dword(fs->win + FSI_Free_Count, fs->free_clst); @@ -1144,7 +1144,7 @@ static DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FF case FS_FAT16 : if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))) != FR_OK) break; - val = ld_word(fs->win + clst * 2 % SS(fs)); /* Simple WORD array */ + val = ld_WORD16(fs->win + clst * 2 % SS(fs)); /* Simple WORD16 array */ break; case FS_FAT32 : @@ -1223,7 +1223,7 @@ static FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */ case FS_FAT16 : res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))); if (res != FR_OK) break; - st_word(fs->win + clst * 2 % SS(fs), (WORD)val); /* Simple WORD array */ + st_WORD16(fs->win + clst * 2 % SS(fs), (WORD16)val); /* Simple WORD16 array */ fs->wflag = 1; break; @@ -1808,9 +1808,9 @@ static DWORD ld_clust ( /* Returns the top cluster value of the SFN entry */ { DWORD cl; - cl = ld_word(dir + DIR_FstClusLO); + cl = ld_WORD16(dir + DIR_FstClusLO); if (fs->fs_type == FS_FAT32) { - cl |= (DWORD)ld_word(dir + DIR_FstClusHI) << 16; + cl |= (DWORD)ld_WORD16(dir + DIR_FstClusHI) << 16; } return cl; @@ -1824,9 +1824,9 @@ static void st_clust ( DWORD cl /* Value to be set */ ) { - st_word(dir + DIR_FstClusLO, (WORD)cl); + st_WORD16(dir + DIR_FstClusLO, (WORD16)cl); if (fs->fs_type == FS_FAT32) { - st_word(dir + DIR_FstClusHI, (WORD)(cl >> 16)); + st_WORD16(dir + DIR_FstClusHI, (WORD16)(cl >> 16)); } } #endif @@ -1847,12 +1847,12 @@ static int cmp_lfn ( /* 1:matched, 0:not matched */ WCHAR wc, uc; - if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */ + if (ld_WORD16(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */ i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ - uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + uc = ld_WORD16(dir + LfnOfs[s]); /* Pick an LFN character */ if (wc != 0) { if (i >= FF_MAX_LFN + 1 || ff_wtoupper(uc) != ff_wtoupper(lfnbuf[i++])) { /* Compare it */ return 0; /* Not matched */ @@ -1883,12 +1883,12 @@ static int pick_lfn ( /* 1:succeeded, 0:buffer overflow or invalid LFN entry * WCHAR wc, uc; - if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO is 0 */ + if (ld_WORD16(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO is 0 */ i = ((dir[LDIR_Ord] & ~LLEF) - 1) * 13; /* Offset in the LFN buffer */ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ - uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + uc = ld_WORD16(dir + LfnOfs[s]); /* Pick an LFN character */ if (wc != 0) { if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */ lfnbuf[i++] = wc = uc; /* Store it */ @@ -1926,13 +1926,13 @@ static void put_lfn ( dir[LDIR_Chksum] = sum; /* Set checksum */ dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */ dir[LDIR_Type] = 0; - st_word(dir + LDIR_FstClusLO, 0); + st_WORD16(dir + LDIR_FstClusLO, 0); i = (ord - 1) * 13; /* Get offset in the LFN working buffer */ s = wc = 0; do { if (wc != 0xFFFF) wc = lfn[i++]; /* Get an effective character */ - st_word(dir + LfnOfs[s], wc); /* Put it */ + st_WORD16(dir + LfnOfs[s], wc); /* Put it */ if (wc == 0) wc = 0xFFFF; /* Padding characters for left locations */ } while (++s < 13); if (wc == 0xFFFF || !lfn[i]) ord |= LLEF; /* Last LFN part is the start of LFN sequence */ @@ -2029,12 +2029,12 @@ static BYTE sum_sfn ( /* exFAT: Checksum */ /*-----------------------------------------------------------------------*/ -static WORD xdir_sum ( /* Get checksum of the directoly entry block */ +static WORD16 xdir_sum ( /* Get checksum of the directoly entry block */ const BYTE* dir /* Directory entry block to be calculated */ ) { UINT i, szblk; - WORD sum; + WORD16 sum; szblk = (dir[XDIR_NumSec] + 1) * SZDIRE; /* Number of bytes of the entry block */ @@ -2050,12 +2050,12 @@ static WORD xdir_sum ( /* Get checksum of the directoly entry block */ -static WORD xname_sum ( /* Get check sum (to be used as hash) of the file name */ +static WORD16 xname_sum ( /* Get check sum (to be used as hash) of the file name */ const WCHAR* name /* File name to be calculated */ ) { WCHAR chr; - WORD sum = 0; + WORD16 sum = 0; while ((chr = *name++) != 0) { @@ -2098,7 +2098,7 @@ static void get_xfileinfo ( while (nc < dirb[XDIR_NumName]) { if (si >= MAXDIRB(FF_MAX_LFN)) { di = 0; break; } /* Truncated directory block? */ if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */ - wc = ld_word(dirb + si); si += 2; nc++; /* Get a character */ + wc = ld_WORD16(dirb + si); si += 2; nc++; /* Get a character */ if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */ hs = wc; continue; /* Get low surrogate */ } @@ -2114,8 +2114,8 @@ static void get_xfileinfo ( fno->fattrib = dirb[XDIR_Attr]; /* Attribute */ fno->fsize = (fno->fattrib & AM_DIR) ? 0 : ld_qword(dirb + XDIR_FileSize); /* Size */ - fno->ftime = ld_word(dirb + XDIR_ModTime + 0); /* Time */ - fno->fdate = ld_word(dirb + XDIR_ModTime + 2); /* Date */ + fno->ftime = ld_WORD16(dirb + XDIR_ModTime + 0); /* Time */ + fno->fdate = ld_WORD16(dirb + XDIR_ModTime + 2); /* Date */ } #endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */ @@ -2166,7 +2166,7 @@ static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ /* Sanity check (do it for only accessible object) */ if (i <= MAXDIRB(FF_MAX_LFN)) { - if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR; + if (xdir_sum(dirb) != ld_WORD16(dirb + XDIR_SetSum)) return FR_INT_ERR; } return FR_OK; } @@ -2232,7 +2232,7 @@ static FRESULT store_xdir ( BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the direcotry entry block 85+C0+C1s */ /* Create set sum */ - st_word(dirb + XDIR_SetSum, xdir_sum(dirb)); + st_WORD16(dirb + XDIR_SetSum, xdir_sum(dirb)); nent = dirb[XDIR_NumSec] + 1; /* Store the direcotry entry block to the directory */ @@ -2277,7 +2277,7 @@ static void create_xdir ( dirb[i++] = ET_FILENAME; dirb[i++] = 0; do { /* Fill name field */ if (wc != 0 && (wc = lfn[nlen]) != 0) nlen++; /* Get a character if exist */ - st_word(dirb + i, wc); /* Store it */ + st_WORD16(dirb + i, wc); /* Store it */ i += 2; } while (i % SZDIRE != 0); nc1++; @@ -2285,7 +2285,7 @@ static void create_xdir ( dirb[XDIR_NumName] = nlen; /* Set name length */ dirb[XDIR_NumSec] = 1 + nc1; /* Set secondary count (C0 + C1s) */ - st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */ + st_WORD16(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */ } #endif /* !FF_FS_READONLY */ @@ -2396,16 +2396,16 @@ static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ BYTE nc; UINT di, ni; - WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */ + WORD16 hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */ while ((res = DIR_READ_FILE(dp)) == FR_OK) { /* Read an item */ #if FF_MAX_LFN < 255 if (fs->dirbuf[XDIR_NumName] > FF_MAX_LFN) continue; /* Skip comparison if inaccessible object name */ #endif - if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash mismatched */ + if (ld_WORD16(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash mismatched */ for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */ if ((di % SZDIRE) == 0) di += 2; - if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break; + if (ff_wtoupper(ld_WORD16(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break; } if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */ } @@ -2705,8 +2705,8 @@ static void get_fileinfo ( fno->fattrib = dp->dir[DIR_Attr]; /* Attribute */ fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */ - fno->ftime = ld_word(dp->dir + DIR_ModTime + 0); /* Time */ - fno->fdate = ld_word(dp->dir + DIR_ModTime + 2); /* Date */ + fno->ftime = ld_WORD16(dp->dir + DIR_ModTime + 0); /* Time */ + fno->fdate = ld_WORD16(dp->dir + DIR_ModTime + 2); /* Date */ } #endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */ @@ -3088,7 +3088,7 @@ static BYTE check_fs ( /* 0:FAT, 1:exFAT, 2:Valid BS but not FAT, 3:Not a BS, 4 fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */ if (move_window(fs, sect) != FR_OK) return 4; /* Load boot record */ - if (ld_word(fs->win + BS_55AA) != 0xAA55) return 3; /* Check boot record signature (always here regardless of the sector size) */ + if (ld_WORD16(fs->win + BS_55AA) != 0xAA55) return 3; /* Check boot record signature (always here regardless of the sector size) */ #if FF_FS_EXFAT if (!mem_cmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1; /* Check if exFAT VBR */ @@ -3115,7 +3115,7 @@ static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred BYTE fmt, *pt; DSTATUS stat; DWORD bsect, fasize, tsect, sysect, nclst, szbfat, br[4]; - WORD nrsv; + WORD16 nrsv; UINT i; @@ -3178,7 +3178,7 @@ static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ; /* Check zero filler */ if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM; - if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT version (must be version 1.0) */ + if (ld_WORD16(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT version (must be version 1.0) */ if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) { /* (BPB_BytsPerSecEx must be equal to the physical sector size) */ return FR_NO_FILESYSTEM; @@ -3234,9 +3234,9 @@ static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred } else #endif /* FF_FS_EXFAT */ { - if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */ + if (ld_WORD16(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */ - fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */ + fasize = ld_WORD16(fs->win + BPB_FATSz16); /* Number of sectors per FAT */ if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32); fs->fsize = fasize; @@ -3247,13 +3247,13 @@ static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */ if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */ - fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */ + fs->n_rootdir = ld_WORD16(fs->win + BPB_RootEntCnt); /* Number of root directory entries */ if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */ - tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */ + tsect = ld_WORD16(fs->win + BPB_TotSec16); /* Number of sectors on the volume */ if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32); - nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */ + nrsv = ld_WORD16(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */ if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */ /* Determine the FAT sub type */ @@ -3273,7 +3273,7 @@ static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred fs->fatbase = bsect + nrsv; /* FAT start sector */ fs->database = bsect + sysect; /* Data start sector */ if (fmt == FS_FAT32) { - if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */ + if (ld_WORD16(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */ if (fs->n_rootdir != 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */ szbfat = fs->n_fatent * 4; /* (Needed FAT size) */ @@ -3291,11 +3291,11 @@ static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred fs->fsi_flag = 0x80; #if (FF_FS_NOFSINFO & 3) != 3 if (fmt == FS_FAT32 /* Allow to update FSInfo only if BPB_FSInfo32 == 1 */ - && ld_word(fs->win + BPB_FSInfo32) == 1 + && ld_WORD16(fs->win + BPB_FSInfo32) == 1 && move_window(fs, bsect + 1) == FR_OK) { fs->fsi_flag = 0; - if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSInfo data if available */ + if (ld_WORD16(fs->win + BS_55AA) == 0xAA55 /* Load FSInfo data if available */ && ld_dword(fs->win + FSI_LeadSig) == 0x41615252 && ld_dword(fs->win + FSI_StrucSig) == 0x61417272) { @@ -3887,7 +3887,7 @@ FRESULT f_sync ( st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation information */ st_dword(dir + DIR_FileSize, (DWORD)fp->obj.objsize); /* Update file size */ st_dword(dir + DIR_ModTime, tm); /* Update modified time */ - st_word(dir + DIR_LstAccDate, 0); + st_WORD16(dir + DIR_LstAccDate, 0); fs->wflag = 1; res = sync_fs(fs); /* Restore it to the directory */ fp->flag &= (BYTE)~FA_MODIFIED; @@ -4538,7 +4538,7 @@ FRESULT f_getfree ( } while (clst); } else #endif - { /* FAT16/32: Scan WORD/DWORD FAT entries */ + { /* FAT16/32: Scan WORD16/DWORD FAT entries */ clst = fs->n_fatent; /* Number of entries */ sect = fs->fatbase; /* Top of the FAT */ i = 0; /* Offset in the sector */ @@ -4548,7 +4548,7 @@ FRESULT f_getfree ( if (res != FR_OK) break; } if (fs->fs_type == FS_FAT16) { - if (ld_word(fs->win + i) == 0) nfree++; + if (ld_WORD16(fs->win + i) == 0) nfree++; i += 2; } else { if ((ld_dword(fs->win + i) & 0x0FFFFFFF) == 0) nfree++; @@ -4830,7 +4830,7 @@ FRESULT f_rename ( #if FF_FS_EXFAT if (fs->fs_type == FS_EXFAT) { /* At exFAT volume */ BYTE nf, nn; - WORD nh; + WORD16 nh; mem_cpy(buf, fs->dirbuf, SZDIRE * 2); /* Save 85+C0 entry of old object */ mem_cpy(&djn, &djo, sizeof djo); @@ -4842,10 +4842,10 @@ FRESULT f_rename ( res = dir_register(&djn); /* Register the new entry */ if (res == FR_OK) { nf = fs->dirbuf[XDIR_NumSec]; nn = fs->dirbuf[XDIR_NumName]; - nh = ld_word(fs->dirbuf + XDIR_NameHash); + nh = ld_WORD16(fs->dirbuf + XDIR_NameHash); mem_cpy(fs->dirbuf, buf, SZDIRE * 2); /* Restore 85+C0 entry */ fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn; - st_word(fs->dirbuf + XDIR_NameHash, nh); + st_WORD16(fs->dirbuf + XDIR_NameHash, nh); if (!(fs->dirbuf[XDIR_Attr] & AM_DIR)) fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */ /* Start of critical section where an interruption can cause a cross-link */ res = store_xdir(&djn); @@ -5031,7 +5031,7 @@ FRESULT f_getlabel ( WCHAR hs; for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */ - wc = ld_word(dj.dir + XDIR_Label + si * 2); + wc = ld_WORD16(dj.dir + XDIR_Label + si * 2); if (hs == 0 && IsSurrogate(wc)) { /* Is the code a surrogate? */ hs = wc; continue; } @@ -5128,13 +5128,13 @@ FRESULT f_setlabel ( if (dc == 0xFFFFFFFF || di >= 10) { /* Wrong surrogate or buffer overflow */ dc = 0; } else { - st_word(dirvn + di * 2, (WCHAR)(dc >> 16)); di++; + st_WORD16(dirvn + di * 2, (WCHAR)(dc >> 16)); di++; } } if (dc == 0 || chk_chr(badchr + 7, (int)dc) || di >= 11) { /* Check validity of the volume label */ LEAVE_FF(fs, FR_INVALID_NAME); } - st_word(dirvn + di * 2, (WCHAR)dc); di++; + st_WORD16(dirvn + di * 2, (WCHAR)dc); di++; } } else #endif @@ -5390,10 +5390,10 @@ FRESULT f_mkfs ( { const UINT n_fats = 1; /* Number of FATs for FAT/FAT32 volume (1 or 2) */ const UINT n_rootdir = 512; /* Number of root directory entries for FAT volume */ - static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT volume (4Ks unit) */ - static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */ + static const WORD16 cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT volume (4Ks unit) */ + static const WORD16 cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */ BYTE fmt, sys, *buf, *pte, part; void *pdrv; - WORD ss; /* Sector size */ + WORD16 ss; /* Sector size */ DWORD szb_buf, sz_buf, sz_blk, n_clst, pau, sect, nsect, n; DWORD b_vol, b_fat, b_data; /* Base LBA for volume, fat, data */ DWORD sz_vol, sz_rsv, sz_fat, sz_dir; /* Size for volume, fat, dir, data */ @@ -5441,7 +5441,7 @@ FRESULT f_mkfs ( if (FF_MULTI_PARTITION && part != 0) { /* Get partition information from partition table in the MBR */ if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load MBR */ - if (ld_word(buf + BS_55AA) != 0xAA55) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if MBR is valid */ + if (ld_WORD16(buf + BS_55AA) != 0xAA55) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if MBR is valid */ pte = buf + (MBR_Table + (part - 1) * SZ_PTE); if (pte[PTE_System] == 0) LEAVE_MKFS(FR_MKFS_ABORTED); /* No partition? */ b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */ @@ -5604,20 +5604,20 @@ FRESULT f_mkfs ( st_dword(buf + BPB_NumClusEx, n_clst); /* Number of clusters */ st_dword(buf + BPB_RootClusEx, 2 + tbl[0] + tbl[1]); /* Root dir cluster # */ st_dword(buf + BPB_VolIDEx, GET_FATTIME()); /* VSN */ - st_word(buf + BPB_FSVerEx, 0x100); /* Filesystem version (1.00) */ + st_WORD16(buf + BPB_FSVerEx, 0x100); /* Filesystem version (1.00) */ for (buf[BPB_BytsPerSecEx] = 0, i = ss; i >>= 1; buf[BPB_BytsPerSecEx]++) ; /* Log2 of sector size [byte] */ for (buf[BPB_SecPerClusEx] = 0, i = au; i >>= 1; buf[BPB_SecPerClusEx]++) ; /* Log2 of cluster size [sector] */ buf[BPB_NumFATsEx] = 1; /* Number of FATs */ buf[BPB_DrvNumEx] = 0x80; /* Drive number (for int13) */ - st_word(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */ - st_word(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */ + st_WORD16(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */ + st_WORD16(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */ for (i = sum = 0; i < ss; i++) { /* VBR checksum */ if (i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx) sum = xsum32(buf[i], sum); } if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Extended bootstrap record (+1..+8) */ mem_set(buf, 0, ss); - st_word(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */ + st_WORD16(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */ for (j = 1; j < 9; j++) { for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); @@ -5714,37 +5714,37 @@ FRESULT f_mkfs ( /* Create FAT VBR */ mem_set(buf, 0, ss); mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */ - st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */ + st_WORD16(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */ buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */ - st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */ + st_WORD16(buf + BPB_RsvdSecCnt, (WORD16)sz_rsv); /* Size of reserved area */ buf[BPB_NumFATs] = (BYTE)n_fats; /* Number of FATs */ - st_word(buf + BPB_RootEntCnt, (WORD)((fmt == FS_FAT32) ? 0 : n_rootdir)); /* Number of root directory entries */ + st_WORD16(buf + BPB_RootEntCnt, (WORD16)((fmt == FS_FAT32) ? 0 : n_rootdir)); /* Number of root directory entries */ if (sz_vol < 0x10000) { - st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */ + st_WORD16(buf + BPB_TotSec16, (WORD16)sz_vol); /* Volume size in 16-bit LBA */ } else { st_dword(buf + BPB_TotSec32, sz_vol); /* Volume size in 32-bit LBA */ } buf[BPB_Media] = 0xF8; /* Media descriptor byte */ - st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */ - st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */ + st_WORD16(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */ + st_WORD16(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */ st_dword(buf + BPB_HiddSec, b_vol); /* Volume offset in the physical drive [sector] */ if (fmt == FS_FAT32) { st_dword(buf + BS_VolID32, GET_FATTIME()); /* VSN */ st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */ st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */ - st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */ - st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */ + st_WORD16(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */ + st_WORD16(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */ buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */ buf[BS_BootSig32] = 0x29; /* Extended boot signature */ mem_cpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */ } else { st_dword(buf + BS_VolID, GET_FATTIME()); /* VSN */ - st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */ + st_WORD16(buf + BPB_FATSz16, (WORD16)sz_fat); /* FAT size [sector] */ buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */ buf[BS_BootSig] = 0x29; /* Extended boot signature */ mem_cpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */ } - st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */ + st_WORD16(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */ if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the VBR sector */ /* Create FSINFO record if needed */ @@ -5755,7 +5755,7 @@ FRESULT f_mkfs ( st_dword(buf + FSI_StrucSig, 0x61417272); st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */ st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */ - st_word(buf + BS_55AA, 0xAA55); + st_WORD16(buf + BS_55AA, 0xAA55); disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */ disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */ } @@ -5813,7 +5813,7 @@ FRESULT f_mkfs ( } else { /* Created as a new single partition */ if (!(opt & FM_SFD)) { /* Create partition table if in FDISK format */ mem_set(buf, 0, ss); - st_word(buf + BS_55AA, 0xAA55); /* MBR signature */ + st_WORD16(buf + BS_55AA, 0xAA55); /* MBR signature */ pte = buf + MBR_Table; /* Create partition table for single partition in the drive */ pte[PTE_Boot] = 0; /* Boot indicator */ pte[PTE_StHead] = 1; /* Start head */ @@ -5904,7 +5904,7 @@ FRESULT f_fdisk ( /* Next partition */ b_cyl += p_cyl; } - st_word(p, 0xAA55); /* MBR signature (always at offset 510) */ + st_WORD16(p, 0xAA55); /* MBR signature (always at offset 510) */ /* Write it to the MBR */ res = (disk_write(pdrv, buf, 0, 1) == RES_OK && disk_ioctl(pdrv, CTRL_SYNC, 0) == RES_OK) ? FR_OK : FR_DISK_ERR; @@ -5922,10 +5922,10 @@ FRESULT f_fdisk ( /*-----------------------------------------------------------------------*/ FRESULT f_setcp ( - WORD cp /* Value to be set as active code page */ + WORD16 cp /* Value to be set as active code page */ ) { - static const WORD validcp[] = { 437, 720, 737, 771, 775, 850, 852, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950, 0}; + static const WORD16 validcp[] = { 437, 720, 737, 771, 775, 850, 852, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950, 0}; static const BYTE* const tables[] = {Ct437, Ct720, Ct737, Ct771, Ct775, Ct850, Ct852, Ct857, Ct860, Ct861, Ct862, Ct863, Ct864, Ct865, Ct866, Ct869, Dc932, Dc936, Dc949, Dc950, 0}; UINT i; diff --git a/lib/oofatfs/ff.h b/lib/oofatfs/ff.h index a8aa00e9a..cfa2168c5 100644 --- a/lib/oofatfs/ff.h +++ b/lib/oofatfs/ff.h @@ -48,7 +48,7 @@ typedef unsigned __int64 QWORD; #include typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ typedef unsigned char BYTE; /* char must be 8-bit */ -typedef uint16_t WORD; /* 16-bit unsigned integer */ +typedef uint16_t WORD16; /* 16-bit unsigned integer */ typedef uint16_t WCHAR; /* 16-bit unsigned integer */ typedef uint32_t DWORD; /* 32-bit unsigned integer */ typedef uint64_t QWORD; /* 64-bit unsigned integer */ @@ -56,7 +56,7 @@ typedef uint64_t QWORD; /* 64-bit unsigned integer */ #define FF_INTDEF 1 typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ typedef unsigned char BYTE; /* char must be 8-bit */ -typedef unsigned short WORD; /* 16-bit unsigned integer */ +typedef unsigned short WORD16; /* 16-bit unsigned integer */ typedef unsigned short WCHAR; /* 16-bit unsigned integer */ typedef unsigned long DWORD; /* 32-bit unsigned integer */ #endif @@ -125,11 +125,11 @@ typedef struct { BYTE n_fats; /* Number of FATs (1 or 2) */ BYTE wflag; /* win[] flag (b0:dirty) */ BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ - WORD id; /* Volume mount ID */ - WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ - WORD csize; /* Cluster size [sectors] */ + WORD16 id; /* Volume mount ID */ + WORD16 n_rootdir; /* Number of root directory entries (FAT12/16) */ + WORD16 csize; /* Cluster size [sectors] */ #if FF_MAX_SS != FF_MIN_SS - WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ + WORD16 ssize; /* Sector size (512, 1024, 2048 or 4096) */ #endif #if FF_USE_LFN WCHAR* lfnbuf; /* LFN working buffer */ @@ -171,7 +171,7 @@ typedef struct { typedef struct { FATFS* fs; /* Pointer to the hosting volume of this object */ - WORD id; /* Hosting volume mount ID */ + WORD16 id; /* Hosting volume mount ID */ BYTE attr; /* Object attribute */ BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ @@ -236,8 +236,8 @@ typedef struct { typedef struct { FSIZE_t fsize; /* File size */ - WORD fdate; /* Modified date */ - WORD ftime; /* Modified time */ + WORD16 fdate; /* Modified date */ + WORD16 ftime; /* Modified time */ BYTE fattrib; /* File attribute */ #if FF_USE_LFN TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ @@ -308,7 +308,7 @@ FRESULT f_mount (FATFS* fs); /* Mount/Unm FRESULT f_umount (FATFS* fs); /* Unmount a logical drive */ FRESULT f_mkfs (FATFS *fs, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */ FRESULT f_fdisk (void *pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */ -FRESULT f_setcp (WORD cp); /* Set current code page */ +FRESULT f_setcp (WORD16 cp); /* Set current code page */ #define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) #define f_error(fp) ((fp)->err) @@ -336,8 +336,8 @@ DWORD get_fattime (void); /* LFN support functions */ #if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */ -WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ -WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ +WCHAR ff_oem2uni (WCHAR oem, WORD16 cp); /* OEM code to Unicode conversion */ +WCHAR ff_uni2oem (DWORD uni, WORD16 cp); /* Unicode to OEM code conversion */ DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ #endif #if FF_USE_LFN == 3 /* Dynamic memory allocation */ diff --git a/lib/timeutils/timeutils.c b/lib/timeutils/timeutils.c index fc8b5e7fc..af210d994 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 14da831dc..2d40f773c 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; diff --git a/lib/upytesthelper/upytesthelper.c b/lib/upytesthelper/upytesthelper.c index 326172be6..a9a4efa64 100644 --- a/lib/upytesthelper/upytesthelper.c +++ b/lib/upytesthelper/upytesthelper.c @@ -87,7 +87,42 @@ void upytest_output(const char *str, mp_uint_t len) { } mp_hal_stdout_tx_strn_cooked(str, len); } +#if NO_NLR +void upytest_execute_test(const char *src) { + // To provide clean room for each test, interpreter and heap are + // reinitialized before running each. + gc_init(heap_start, heap_end); + mp_init(); + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_init(mp_sys_argv, 0); + + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); + if (module_fun != MP_OBJ_NULL) { + mp_call_function_0(module_fun); + } + if (MP_STATE_THREAD(active_exception) != NULL) { + mp_obj_t exc = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + if (mp_obj_is_subclass_fast(mp_obj_get_type(exc), &mp_type_SystemExit)) { + // Assume that sys.exit() is called to skip the test. That can be always true, we should +#pragma message "TODO: set up convention to use specific exit code as skip indicator." + tinytest_set_test_skipped_(); + goto end; + } + mp_obj_print_exception(&mp_plat_print, exc); + tt_abort_msg("Uncaught exception\n"); + } + + if (upytest_is_failed()) { + tinytest_set_test_failed_(); + } +end: + mp_deinit(); +} +#else void upytest_execute_test(const char *src) { // To provide clean room for each test, interpreter and heap are // reinitialized before running each. @@ -124,3 +159,4 @@ void upytest_execute_test(const char *src) { end: mp_deinit(); } +#endif diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index 2c8ca2de0..c7cb9d61d 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -39,16 +39,18 @@ #include "irq.h" #include "usb.h" #endif + #include "lib/mp-readline/readline.h" + #include "lib/utils/pyexec.h" #include "genhdr/mpversion.h" +// #include "lib/utils/interrupt_char.h" // for mp_hal_set_interrupt_char +extern void mp_hal_set_interrupt_char(int c); + pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; int pyexec_system_exit = 0; - -#if MICROPY_REPL_INFO STATIC bool repl_display_debugging_info = 0; -#endif #define EXEC_FLAG_PRINT_EOF (1) #define EXEC_FLAG_ALLOW_DEBUGGING (2) @@ -62,17 +64,66 @@ STATIC bool repl_display_debugging_info = 0; // EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output // EXEC_FLAG_ALLOW_DEBUGGING allows debugging info to be printed after executing the code // EXEC_FLAG_IS_REPL is used for REPL inputs (flag passed on to mp_compile) -STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, int exec_flags) { - int ret = 0; - #if MICROPY_REPL_INFO - uint32_t start = 0; + + + +#if NO_NLR + #include "../wapy/upython.h" + + #if defined(__EMSCRIPTEN__) + #pragma message "TODO: pyexec->no_nlr is crude" + #include "emscripten.h" #endif - // by default a SystemExit exception returns 0 - pyexec_system_exit = 0; - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { + #define FORCED_EXIT (0x100) + // If exc is SystemExit, return value where FORCED_EXIT bit set, + // and lower 8 bits are SystemExit value. For all other exceptions, + // return 1. + + STATIC void stderr_print_strn(void *env, const char *str, size_t len) { + (void)env; + mp_hal_stdout_tx_strn(str,len); + } + + const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; + + + STATIC int handle_uncaught_exception(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + //clog("89:SystemExit"); + // None is an exit value of 0; an int is its value; anything else is 1 + /* + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + */ + #if defined(__EMSCRIPTEN__) + EM_ASM({console.log("91:SystemExit");}); + #endif + + + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + // Report all other exceptions + mp_obj_print_exception(&mp_stderr_print, MP_OBJ_FROM_PTR(exc)); + return 0; + } + + STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, int exec_flags) { + int retval = 0; + uint32_t start = 0; + + // by default a SystemExit exception returns 0 + pyexec_system_exit = 0; + + mp_obj_t module_fun; #if MICROPY_MODULE_FROZEN_MPY if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) { @@ -89,462 +140,158 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input } else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) { lex = mp_lexer_new_from_file(source); } else { - lex = (mp_lexer_t *)source; + lex = (mp_lexer_t*)source; } // source is a lexer, parse and compile the script qstr source_name = lex->source_name; mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); #else - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported")); + mp_raise_msg(&mp_type_RuntimeError, "script compilation not supported"); #endif } - // execute code - mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us - #if MICROPY_REPL_INFO - start = mp_hal_ticks_ms(); - #endif - mp_call_function_0(module_fun); - mp_hal_set_interrupt_char(-1); // disable interrupt - mp_handle_pending(true); // handle any pending exceptions (and any callbacks) - nlr_pop(); - ret = 1; - if (exec_flags & EXEC_FLAG_PRINT_EOF) { - mp_hal_stdout_tx_strn("\x04", 1); - } - } else { - // uncaught exception - mp_hal_set_interrupt_char(-1); // disable interrupt - mp_handle_pending(false); // clear any pending exceptions (and run any callbacks) - // print EOF after normal output - if (exec_flags & EXEC_FLAG_PRINT_EOF) { - mp_hal_stdout_tx_strn("\x04", 1); - } - // check for SystemExit - if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { - // at the moment, the value of SystemExit is unused - ret = pyexec_system_exit; - } else { - mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); - ret = 0; - } - } - - #if MICROPY_REPL_INFO - // display debugging info if wanted - if ((exec_flags & EXEC_FLAG_ALLOW_DEBUGGING) && repl_display_debugging_info) { - mp_uint_t ticks = mp_hal_ticks_ms() - start; // TODO implement a function that does this properly - printf("took " UINT_FMT " ms\n", ticks); - // qstr info - { - size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; - qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); - printf("qstr:\n n_pool=%u\n n_qstr=%u\n " - "n_str_data_bytes=%u\n n_total_bytes=%u\n", - (unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes); - } - - #if MICROPY_ENABLE_GC - // run collection and print GC info - gc_collect(); - gc_dump_info(); - #endif - } - #endif - - if (exec_flags & EXEC_FLAG_PRINT_EOF) { - mp_hal_stdout_tx_strn("\x04", 1); - } - - return ret; -} -#if MICROPY_ENABLE_COMPILER -#if MICROPY_REPL_EVENT_DRIVEN - -typedef struct _repl_t { - // This structure originally also held current REPL line, - // but it was moved to MP_STATE_VM(repl_line) as containing - // root pointer. Still keep structure in case more state - // will be added later. - // vstr_t line; - bool cont_line; - bool paste_mode; -} repl_t; - -repl_t repl; - -STATIC int pyexec_raw_repl_process_char(int c); -STATIC int pyexec_friendly_repl_process_char(int c); - -void pyexec_event_repl_init(void) { - MP_STATE_VM(repl_line) = vstr_new(32); - repl.cont_line = false; - repl.paste_mode = false; - // no prompt before printing friendly REPL banner or entering raw REPL - readline_init(MP_STATE_VM(repl_line), ""); - if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { - pyexec_raw_repl_process_char(CHAR_CTRL_A); - } else { - pyexec_friendly_repl_process_char(CHAR_CTRL_B); - } -} + if ( module_fun != MP_OBJ_NULL) { + // execute code + mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us + start = mp_hal_ticks_ms(); + mp_obj_t ret = mp_call_function_0(module_fun); + mp_hal_set_interrupt_char(-1); // disable interrupt -STATIC int pyexec_raw_repl_process_char(int c) { - if (c == CHAR_CTRL_A) { - // reset raw REPL - mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); - goto reset; - } else if (c == CHAR_CTRL_B) { - // change to friendly REPL - pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; - vstr_reset(MP_STATE_VM(repl_line)); - repl.cont_line = false; - repl.paste_mode = false; - pyexec_friendly_repl_process_char(CHAR_CTRL_B); - return 0; - } else if (c == CHAR_CTRL_C) { - // clear line - vstr_reset(MP_STATE_VM(repl_line)); - return 0; - } else if (c == CHAR_CTRL_D) { - // input finished - } else { - // let through any other raw 8-bit value - vstr_add_byte(MP_STATE_VM(repl_line), c); - return 0; - } + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); + } - // indicate reception of command - mp_hal_stdout_tx_str("OK"); + if (ret != MP_OBJ_NULL && MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + } - if (MP_STATE_VM(repl_line)->len == 0) { - // exit for a soft reset - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(MP_STATE_VM(repl_line)); - return PYEXEC_FORCED_EXIT; - } + if (MP_STATE_THREAD(active_exception) != NULL) { + // uncaught exception + return handle_uncaught_exception(); + } + } - int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); - if (ret & PYEXEC_FORCED_EXIT) { - return ret; + return retval; } -reset: - vstr_reset(MP_STATE_VM(repl_line)); - mp_hal_stdout_tx_str(">"); - - return 0; -} +#else + STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, int exec_flags) { + int ret = 0; + uint32_t start = 0; + + // by default a SystemExit exception returns 0 + pyexec_system_exit = 0; + + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t module_fun; + #if MICROPY_MODULE_FROZEN_MPY + if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) { + // source is a raw_code object, create the function + module_fun = mp_make_function_from_raw_code(source, MP_OBJ_NULL, MP_OBJ_NULL); + } else + #endif + { + #if MICROPY_ENABLE_COMPILER + mp_lexer_t *lex; + 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_FILENAME) { + lex = mp_lexer_new_from_file(source); + } else { + lex = (mp_lexer_t*)source; + } + // source is a lexer, parse and compile the script + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL); + #else + mp_raise_msg(&mp_type_RuntimeError, "script compilation not supported"); + #endif + } -STATIC int pyexec_friendly_repl_process_char(int c) { - if (repl.paste_mode) { - if (c == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\r\n"); - goto input_restart; - } else if (c == CHAR_CTRL_D) { - // end of input - mp_hal_stdout_tx_str("\r\n"); - int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); - if (ret & PYEXEC_FORCED_EXIT) { - return ret; + // execute code + mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us + start = mp_hal_ticks_ms(); + mp_call_function_0(module_fun); + mp_hal_set_interrupt_char(-1); // disable interrupt + nlr_pop(); + ret = 1; + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); } - goto input_restart; } else { - // add char to buffer and echo - vstr_add_byte(MP_STATE_VM(repl_line), c); - if (c == '\r') { - mp_hal_stdout_tx_str("\r\n=== "); - } else { - char buf[1] = {c}; - mp_hal_stdout_tx_strn(buf, 1); + // uncaught exception + // FIXME it could be that an interrupt happens just before we disable it here + mp_hal_set_interrupt_char(-1); // disable interrupt + // print EOF after normal output + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); } - return 0; - } - } - - int ret = readline_process_char(c); - - if (!repl.cont_line) { - - if (ret == CHAR_CTRL_A) { - // change to raw REPL - pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; - mp_hal_stdout_tx_str("\r\n"); - pyexec_raw_repl_process_char(CHAR_CTRL_A); - return 0; - } else if (ret == CHAR_CTRL_B) { - // reset friendly REPL - mp_hal_stdout_tx_str("\r\n"); - 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 - mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); - #endif - goto input_restart; - } else if (ret == CHAR_CTRL_C) { - // break - mp_hal_stdout_tx_str("\r\n"); - goto input_restart; - } else if (ret == CHAR_CTRL_D) { - // exit for a soft reset - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(MP_STATE_VM(repl_line)); - return PYEXEC_FORCED_EXIT; - } else if (ret == CHAR_CTRL_E) { - // paste mode - mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); - vstr_reset(MP_STATE_VM(repl_line)); - repl.paste_mode = true; - return 0; - } - - if (ret < 0) { - return 0; - } - - if (!mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { - goto exec; - } - - vstr_add_byte(MP_STATE_VM(repl_line), '\n'); - repl.cont_line = true; - readline_note_newline("... "); - return 0; - - } else { - - if (ret == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\r\n"); - repl.cont_line = false; - goto input_restart; - } else if (ret == CHAR_CTRL_D) { - // stop entering compound statement - goto exec; - } - - if (ret < 0) { - return 0; - } - - if (mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { - vstr_add_byte(MP_STATE_VM(repl_line), '\n'); - readline_note_newline("... "); - return 0; - } - - exec:; - int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_SINGLE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); - if (ret & PYEXEC_FORCED_EXIT) { - return ret; - } - - input_restart: - vstr_reset(MP_STATE_VM(repl_line)); - repl.cont_line = false; - repl.paste_mode = false; - readline_init(MP_STATE_VM(repl_line), ">>> "); - return 0; - } -} - -uint8_t pyexec_repl_active; -int pyexec_event_repl_process_char(int c) { - pyexec_repl_active = 1; - int res; - if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { - res = pyexec_raw_repl_process_char(c); - } else { - res = pyexec_friendly_repl_process_char(c); - } - pyexec_repl_active = 0; - return res; -} - -#else // MICROPY_REPL_EVENT_DRIVEN - -int pyexec_raw_repl(void) { - vstr_t line; - vstr_init(&line, 32); - -raw_repl_reset: - mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); - - for (;;) { - vstr_reset(&line); - mp_hal_stdout_tx_str(">"); - for (;;) { - int c = mp_hal_stdin_rx_chr(); - if (c == CHAR_CTRL_A) { - // reset raw REPL - goto raw_repl_reset; - } else if (c == CHAR_CTRL_B) { - // change to friendly REPL - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; - return 0; - } else if (c == CHAR_CTRL_C) { - // clear line - vstr_reset(&line); - } else if (c == CHAR_CTRL_D) { - // input finished - break; + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // at the moment, the value of SystemExit is unused + ret = pyexec_system_exit; } else { - // let through any other raw 8-bit value - vstr_add_byte(&line, c); + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + ret = 0; } } - // indicate reception of command - mp_hal_stdout_tx_str("OK"); + // display debugging info if wanted + if ((exec_flags & EXEC_FLAG_ALLOW_DEBUGGING) && repl_display_debugging_info) { + mp_uint_t ticks = mp_hal_ticks_ms() - start; // TODO implement a function that does this properly + printf("took " UINT_FMT " ms\n", ticks); + // qstr info + { + size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes; + qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes); + printf("qstr:\n n_pool=%u\n n_qstr=%u\n " + "n_str_data_bytes=%u\n n_total_bytes=%u\n", + (unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes); + } - if (line.len == 0) { - // exit for a soft reset - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - return PYEXEC_FORCED_EXIT; + #if MICROPY_ENABLE_GC + // run collection and print GC info + gc_collect(); + gc_dump_info(); + #endif } - int ret = parse_compile_execute(&line, MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); - if (ret & PYEXEC_FORCED_EXIT) { - return ret; + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + mp_hal_stdout_tx_strn("\x04", 1); } - } -} -int pyexec_friendly_repl(void) { - vstr_t line; - vstr_init(&line, 32); + return ret; + } +#endif - #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 - mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); - #endif +#if MICROPY_ENABLE_COMPILER - // to test ctrl-C - /* - { - uint32_t x[4] = {0x424242, 0xdeaddead, 0x242424, 0xdeadbeef}; - for (;;) { - nlr_buf_t nlr; - printf("pyexec_repl: %p\n", x); - mp_hal_set_interrupt_char(CHAR_CTRL_C); - if (nlr_push(&nlr) == 0) { - for (;;) { - } - } else { - printf("break\n"); - } - } - } - */ - - for (;;) { - input_restart: - - #if MICROPY_HW_ENABLE_USB - if (usb_vcp_is_enabled()) { - // If the user gets to here and interrupts are disabled then - // they'll never see the prompt, traceback etc. The USB REPL needs - // interrupts to be enabled or no transfers occur. So we try to - // do the user a favor and reenable interrupts. - if (query_irq() == IRQ_STATE_DISABLED) { - enable_irq(IRQ_STATE_ENABLED); - mp_hal_stdout_tx_str("MPY: enabling IRQs\r\n"); - } - } + #if MICROPY_REPL_EVENT_DRIVEN + #if NO_NLR + #include "../wapy/repl.c" + #else + #include "../wapy/repl_nlr.c" #endif - // If the GC is locked at this point there is no way out except a reset, - // so force the GC to be unlocked to help the user debug what went wrong. - if (MP_STATE_MEM(gc_lock_depth) != 0) { - MP_STATE_MEM(gc_lock_depth) = 0; - } - - vstr_reset(&line); - int ret = readline(&line, ">>> "); - mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; - - if (ret == CHAR_CTRL_A) { - // change to raw REPL - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; - return 0; - } else if (ret == CHAR_CTRL_B) { - // reset friendly REPL - mp_hal_stdout_tx_str("\r\n"); - goto friendly_repl_reset; - } else if (ret == CHAR_CTRL_C) { - // break - mp_hal_stdout_tx_str("\r\n"); - continue; - } else if (ret == CHAR_CTRL_D) { - // exit for a soft reset - mp_hal_stdout_tx_str("\r\n"); - vstr_clear(&line); - return PYEXEC_FORCED_EXIT; - } else if (ret == CHAR_CTRL_E) { - // paste mode - mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); - vstr_reset(&line); - for (;;) { - char c = mp_hal_stdin_rx_chr(); - if (c == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\r\n"); - goto input_restart; - } else if (c == CHAR_CTRL_D) { - // end of input - mp_hal_stdout_tx_str("\r\n"); - break; - } else { - // add char to buffer and echo - vstr_add_byte(&line, c); - if (c == '\r') { - mp_hal_stdout_tx_str("\r\n=== "); - } else { - mp_hal_stdout_tx_strn(&c, 1); - } - } - } - parse_input_kind = MP_PARSE_FILE_INPUT; - } else if (vstr_len(&line) == 0) { - continue; - } else { - // got a line with non-zero length, see if it needs continuing - while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { - vstr_add_byte(&line, '\n'); - ret = readline(&line, "... "); - if (ret == CHAR_CTRL_C) { - // cancel everything - mp_hal_stdout_tx_str("\r\n"); - goto input_restart; - } else if (ret == CHAR_CTRL_D) { - // stop entering compound statement - break; - } - } - } + #if 0 + extern repl_t repl; + extern int pyexec_repl_repl_restart(int ret); + extern int pyexec_raw_repl_process_char(int c); + extern int pyexec_friendly_repl_process_char(int c); + #endif - ret = parse_compile_execute(&line, parse_input_kind, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); - if (ret & PYEXEC_FORCED_EXIT) { - return ret; - } - } -} + #else // MICROPY_REPL_EVENT_DRIVEN + #pragma message "[redacted]" + #endif // MICROPY_REPL_EVENT_DRIVEN -#endif // MICROPY_REPL_EVENT_DRIVEN #endif // MICROPY_ENABLE_COMPILER int pyexec_file(const char *filename) { @@ -586,10 +333,9 @@ int pyexec_frozen_module(const char *name) { } #endif -#if MICROPY_REPL_INFO mp_obj_t pyb_set_repl_info(mp_obj_t o_value) { repl_display_debugging_info = mp_obj_get_int(o_value); return mp_const_none; } + MP_DEFINE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj, pyb_set_repl_info); -#endif diff --git a/mpy-cross/Makefile b/mpy-cross/Makefile index f80ee761b..6c99ca49e 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 @@ -44,6 +44,8 @@ LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections endif LDFLAGS = $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) +include ../ports/wapy/wapy.mk + # source files SRC_C = \ main.c \ diff --git a/mpy-cross/main.c b/mpy-cross/main.c index a403c0504..a93ee7d55 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -38,7 +38,7 @@ #ifdef _WIN32 #include "ports/windows/fmode.h" #endif - +mp_state_ctx_t mp_state_ctx; // Command line options, with their defaults STATIC uint emit_opt = MP_EMIT_OPT_NONE; mp_uint_t mp_verbose_flag = 0; @@ -47,14 +47,92 @@ mp_uint_t mp_verbose_flag = 0; // Make it larger on a 64 bit machine, because pointers are larger. long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4); -STATIC void stderr_print_strn(void *env, const char *str, size_t len) { +#if __EMSCRIPTEN__ +#include "emscripten.h" + +STATIC void stderr_print_strn(void *env, const char *str, mp_uint_t len) { +EM_ASM{ + console.error("ERROR"); +} +} +#else +STATIC void stderr_print_strn(void *env, const char *str, mp_uint_t len) { (void)env; ssize_t dummy = write(STDERR_FILENO, str, len); (void)dummy; } +#endif STATIC const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; +#if NO_NLR + + +STATIC void stderr_print_strn2(void *env, const char *str, size_t len) { + (void)env; + fprintf(stderr, "%s", str); +} + +const mp_print_t mp_stderr_print2 = {NULL, stderr_print_strn2}; + +int uncaught_exception_handler(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + #if __EMSCRIPTEN__ + EM_ASM({console.log("91:SystemExit");}); + #endif + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + // Report all other exceptions + mp_obj_print_exception(&mp_stderr_print2, MP_OBJ_FROM_PTR(exc)); + return 0; +} + +STATIC int compile_and_save(const char *file, const char *output_file, const char *source_file) { + mp_lexer_t *lex = mp_lexer_new_from_file(file); + if (lex == NULL) { + uncaught_exception_handler(); + return 1; + } else { + qstr source_name; + if (source_file == NULL) { + source_name = lex->source_name; + } else { + source_name = qstr_from_str(source_file); + } + + #if MICROPY_PY___FILE__ + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + #endif + + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_raw_code_t *rc = mp_compile_to_raw_code(&parse_tree, source_name, false); + + if ( rc != MP_OBJ_NULL) { + vstr_t vstr; + vstr_init(&vstr, 16); + if (output_file == NULL) { + vstr_add_str(&vstr, file); + vstr_cut_tail_bytes(&vstr, 2); + vstr_add_str(&vstr, "mpy"); + } else { + vstr_add_str(&vstr, output_file); + } + mp_raw_code_save_file(rc, vstr_null_terminated_str(&vstr)); + vstr_clear(&vstr); +#pragma message "//TODO: MP_STATE_VM(mp_pending_exception)" + return 0; + } + + } + if (MP_STATE_THREAD(active_exception) != NULL) { + uncaught_exception_handler(); + } + return 1; +} +#else STATIC int compile_and_save(const char *file, const char *output_file, const char *source_file) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { @@ -94,6 +172,7 @@ STATIC int compile_and_save(const char *file, const char *output_file, const cha return 1; } } +#endif STATIC int usage(char **argv) { printf( @@ -170,7 +249,7 @@ STATIC void pre_process_options(int argc, char **argv) { heap_size *= 1024 * 1024; } if (word_adjust) { - heap_size = heap_size * BYTES_PER_WORD / 4; + heap_size = heap_size * MP_BYTES_PER_OBJ_WORD / 4; } } else { exit(usage(argv)); @@ -182,7 +261,7 @@ STATIC void pre_process_options(int argc, char **argv) { } MP_NOINLINE int main_(int argc, char **argv) { - mp_stack_set_limit(40000 * (BYTES_PER_WORD / 4)); + mp_stack_set_limit(40000 * (sizeof(void *) / 4)); pre_process_options(argc, argv); @@ -202,7 +281,7 @@ MP_NOINLINE int main_(int argc, char **argv) { #else (void)emit_opt; #endif - +#if MICROPY_DYNAMIC_COMPILER // set default compiler configuration mp_dynamic_compiler.small_int_bits = 31; mp_dynamic_compiler.opt_cache_map_lookup_in_bytecode = 0; @@ -220,7 +299,7 @@ MP_NOINLINE int main_(int argc, char **argv) { mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_NONE; mp_dynamic_compiler.nlr_buf_num_regs = 0; #endif - +#endif const char *input_file = NULL; const char *output_file = NULL; const char *source_file = NULL; @@ -256,6 +335,7 @@ MP_NOINLINE int main_(int argc, char **argv) { } a += 1; source_file = argv[a]; +#if MICROPY_DYNAMIC_COMPILER } else if (strncmp(argv[a], "-msmall-int-bits=", sizeof("-msmall-int-bits=") - 1) == 0) { char *end; mp_dynamic_compiler.small_int_bits = @@ -304,6 +384,12 @@ MP_NOINLINE int main_(int argc, char **argv) { } else { return usage(argv); } +#else + } else if (strcmp(argv[a], "-mno-cache-lookup-bc") == 0) { + + } else if (strcmp(argv[a], "-mcache-lookup-bc") == 0) { + +#endif } else { return usage(argv); } @@ -321,7 +407,14 @@ MP_NOINLINE int main_(int argc, char **argv) { exit(1); } - int ret = compile_and_save(input_file, output_file, source_file); + char pytmpfile[1024]; + + sprintf(pytmpfile, "f2format --simple %s > /tmp/mpy-cross.py", input_file); + system(pytmpfile); + sprintf(pytmpfile, "/tmp/mpy-cross.py"); + + //int ret = compile_and_save(input_file, output_file, source_file); + int ret = compile_and_save(pytmpfile, output_file, source_file); #if MICROPY_PY_MICROPYTHON_MEM_INFO if (mp_verbose_flag) { @@ -345,6 +438,6 @@ uint mp_import_stat(const char *path) { } void nlr_jump_fail(void *val) { - fprintf(stderr, "FATAL: uncaught NLR %p\n", val); + printf("FATAL: uncaught NLR %p\n", val); exit(1); } diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 21d3e12ed..a0d9c000c 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -24,24 +24,56 @@ * THE SOFTWARE. */ -// options to control how MicroPython is built +// options to control how WAPY is built #define MICROPY_ALLOC_PATH_MAX (PATH_MAX) #define MICROPY_PERSISTENT_CODE_LOAD (0) #define MICROPY_PERSISTENT_CODE_SAVE (1) -#define MICROPY_EMIT_X64 (1) -#define MICROPY_EMIT_X86 (1) -#define MICROPY_EMIT_THUMB (1) -#define MICROPY_EMIT_INLINE_THUMB (1) -#define MICROPY_EMIT_INLINE_THUMB_ARMV7M (1) -#define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) -#define MICROPY_EMIT_ARM (1) -#define MICROPY_EMIT_XTENSA (1) -#define MICROPY_EMIT_INLINE_XTENSA (1) -#define MICROPY_EMIT_XTENSAWIN (1) - -#define MICROPY_DYNAMIC_COMPILER (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 + +#ifndef NO_NLR + //#pragma message "NO_NLR was NOT SET !" + #define NO_NLR (1) +#endif +#if NO_NLR + #define MICROPY_ROM_TEXT_COMPRESSION (0) + #define MICROPY_PY_FUNCTION_ATTRS (1) + #define MICROPY_EMIT_WASM (1) + #define MICROPY_PY_BUILTINS_NEXT2 (1) + #define MICROPY_PY_FSTRING (1) + + #define MICROPY_EMIT_X64 (0) + #define MICROPY_EMIT_X86 (0) + #define MICROPY_EMIT_THUMB (0) + #define MICROPY_EMIT_INLINE_THUMB (0) + #define MICROPY_EMIT_INLINE_THUMB_ARMV7M (0) + #define MICROPY_EMIT_INLINE_THUMB_FLOAT (0) + #define MICROPY_EMIT_ARM (0) + #define MICROPY_EMIT_XTENSA (0) + #define MICROPY_EMIT_INLINE_XTENSA (0) + #define MICROPY_EMIT_XTENSAWIN (0) + #define MICROPY_DYNAMIC_COMPILER (0) +#else + #define MICROPY_EMIT_X64 (1) + #define MICROPY_EMIT_X86 (1) + #define MICROPY_EMIT_THUMB (1) + #define MICROPY_EMIT_INLINE_THUMB (1) + #define MICROPY_EMIT_INLINE_THUMB_ARMV7M (1) + #define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) + #define MICROPY_EMIT_ARM (1) + #define MICROPY_EMIT_XTENSA (1) + #define MICROPY_EMIT_INLINE_XTENSA (1) + #define MICROPY_EMIT_XTENSAWIN (1) + #define MICROPY_DYNAMIC_COMPILER (1) +#endif + #define MICROPY_COMP_CONST_FOLDING (1) #define MICROPY_COMP_MODULE_CONST (1) #define MICROPY_COMP_CONST (1) @@ -73,7 +105,7 @@ #define MICROPY_GCREGS_SETJMP (1) #endif -#define MICROPY_PY___FILE__ (0) +#define MICROPY_PY___FILE__ (1) #define MICROPY_PY_ARRAY (0) #define MICROPY_PY_ATTRTUPLE (0) #define MICROPY_PY_COLLECTIONS (0) diff --git a/ports/cc3200/application.mk b/ports/cc3200/application.mk index b216ca16d..2125274ae 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/ftp/ftp.c b/ports/cc3200/ftp/ftp.c index ee80e51f5..37680bc93 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: { diff --git a/ports/cc3200/mods/modmachine.c b/ports/cc3200/mods/modmachine.c index ddecd47f2..89800810c 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" diff --git a/ports/cc3200/mpconfigport.h b/ports/cc3200/mpconfigport.h index 6fdb278d8..87689c505 100644 --- a/ports/cc3200/mpconfigport.h +++ b/ports/cc3200/mpconfigport.h @@ -197,8 +197,6 @@ typedef int32_t mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size typedef long mp_off_t; -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - #define MICROPY_BEGIN_ATOMIC_SECTION() disable_irq() #define MICROPY_END_ATOMIC_SECTION(state) enable_irq(state) #define MICROPY_EVENT_POLL_HOOK __WFI(); diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt new file mode 100644 index 000000000..fa419202f --- /dev/null +++ b/ports/esp32/CMakeLists.txt @@ -0,0 +1,38 @@ +# Top-level cmake file for building MicroPython on ESP32. + +cmake_minimum_required(VERSION 3.5) + +# Set the location of this port's directory. +set(MICROPY_PORT_DIR ${CMAKE_SOURCE_DIR}) + +# Set the board if it's not already set. +if(NOT MICROPY_BOARD) + set(MICROPY_BOARD GENERIC) +endif() + +# Set the board directory and check that it exists. +if(NOT MICROPY_BOARD_DIR) + set(MICROPY_BOARD_DIR ${MICROPY_PORT_DIR}/boards/${MICROPY_BOARD}) +endif() +if(NOT EXISTS ${MICROPY_BOARD_DIR}/mpconfigboard.cmake) + message(FATAL_ERROR "Invalid MICROPY_BOARD specified: ${MICROPY_BOARD}") +endif() + +# Define the output sdkconfig so it goes in the build directory. +set(SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig) + +# Include board config; this is expected to set SDKCONFIG_DEFAULTS (among other options). +include(${MICROPY_BOARD_DIR}/mpconfigboard.cmake) + +# Concatenate all sdkconfig files into a combined one for the IDF to use. +file(WRITE ${CMAKE_BINARY_DIR}/sdkconfig.combined.in "") +foreach(SDKCONFIG_DEFAULT ${SDKCONFIG_DEFAULTS}) + file(READ ${SDKCONFIG_DEFAULT} CONTENTS) + file(APPEND ${CMAKE_BINARY_DIR}/sdkconfig.combined.in "${CONTENTS}") +endforeach() +configure_file(${CMAKE_BINARY_DIR}/sdkconfig.combined.in ${CMAKE_BINARY_DIR}/sdkconfig.combined COPYONLY) +set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) + +# Include main IDF cmake file and define the project. +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(micropython) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index dcf4110cf..124979599 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -1,987 +1,43 @@ -# Select the board to build for: if not given on the command line, -# then default to GENERIC. +# Makefile for MicroPython on ESP32. +# +# This is a simple, convenience wrapper around idf.py (which uses cmake). + +# Select the board to build for, defaulting to GENERIC. BOARD ?= GENERIC # If the build directory is not given, make it reflect the board name. BUILD ?= build-$(BOARD) -BOARD_DIR ?= boards/$(BOARD) -ifeq ($(wildcard $(BOARD_DIR)/.),) -$(error Invalid BOARD specified: $(BOARD_DIR)) -endif - -include ../../py/mkenv.mk - -# Optional (not currently used for ESP32) --include mpconfigport.mk - -ifneq ($(SDKCONFIG),) -$(error Use the BOARD variable instead of SDKCONFIG) -endif - -# Expected to set SDKCONFIG -include $(BOARD_DIR)/mpconfigboard.mk - -# qstr definitions (must come before including py.mk) -QSTR_DEFS = qstrdefsport.h -QSTR_GLOBAL_DEPENDENCIES = $(BOARD_DIR)/mpconfigboard.h -QSTR_GLOBAL_REQUIREMENTS = $(SDKCONFIG_H) - -# MicroPython feature configurations -MICROPY_ROM_TEXT_COMPRESSION ?= 1 -MICROPY_PY_USSL = 0 -MICROPY_SSL_AXTLS = 0 -MICROPY_PY_BTREE = 1 -MICROPY_VFS_FAT = 1 -MICROPY_VFS_LFS2 = 1 - -FROZEN_MANIFEST ?= boards/manifest.py - -# include py core make definitions -include $(TOP)/py/py.mk - -GIT_SUBMODULES = lib/berkeley-db-1.xx - +# Device serial settings. PORT ?= /dev/ttyUSB0 BAUD ?= 460800 -FLASH_MODE ?= dio -FLASH_FREQ ?= 40m -FLASH_SIZE ?= 4MB -CROSS_COMPILE ?= xtensa-esp32-elf- -OBJDUMP = $(CROSS_COMPILE)objdump - -SDKCONFIG_COMBINED = $(BUILD)/sdkconfig.combined -SDKCONFIG_H = $(BUILD)/sdkconfig.h - -# The git hash of the currently supported ESP IDF version. -# These correspond to v3.3.2 and v4.0.1. -ESPIDF_SUPHASH_V3 := 9e70825d1e1cbf7988cf36981774300066580ea7 -ESPIDF_SUPHASH_V4 := 4c81978a3e2220674a432a588292a4c860eef27b - -define print_supported_git_hash -$(info Supported git hash (v3.3): $(ESPIDF_SUPHASH_V3)) -$(info Supported git hash (v4.0) (experimental): $(ESPIDF_SUPHASH_V4)) -endef - -# paths to ESP IDF and its components -ifeq ($(ESPIDF),) -ifneq ($(IDF_PATH),) -ESPIDF = $(IDF_PATH) -else -$(info The ESPIDF variable has not been set, please set it to the root of the esp-idf repository.) -$(info See README.md for installation instructions.) -dummy := $(call print_supported_git_hash) -$(error ESPIDF not set) -endif -endif - -ESPCOMP = $(ESPIDF)/components -ESPTOOL ?= $(ESPCOMP)/esptool_py/esptool/esptool.py -ESPCOMP_KCONFIGS = $(shell find $(ESPCOMP) -name Kconfig) -ESPCOMP_KCONFIGS_PROJBUILD = $(shell find $(ESPCOMP) -name Kconfig.projbuild) - -# verify the ESP IDF version -ESPIDF_CURHASH := $(shell git -C $(ESPIDF) show -s --pretty=format:'%H') - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V3)) -$(info Building with ESP IDF v3) -else ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -$(info Building with ESP IDF v4) - -PYPARSING_VERSION = $(shell python3 -c 'import pyparsing; print(pyparsing.__version__)') -ifneq ($(PYPARSING_VERSION),2.3.1) -$(info ** ERROR **) -$(info EDP IDF requires pyparsing version less than 2.4) -$(info You will need to set up a Python virtual environment with pyparsing 2.3.1) -$(info Please see README.md for more information) -$(error Incorrect pyparsing version) -endif -else -$(info ** WARNING **) -$(info The git hash of ESP IDF does not match the supported version) -$(info The build may complete and the firmware may work but it is not guaranteed) -$(info ESP IDF path: $(ESPIDF)) -$(info Current git hash: $(ESPIDF_CURHASH)) -dummy := $(call print_supported_git_hash) -endif - -# pretty format of ESP IDF version, used internally by the IDF -IDF_VER := $(shell git -C $(ESPIDF) describe) - -ifeq ($(shell which $(CC) 2> /dev/null),) -$(info ** ERROR **) -$(info Cannot find C compiler $(CC)) -$(info Add the xtensa toolchain to your PATH. See README.md) -$(error C compiler missing) -endif - -# Support BLE by default. -# Can be explicitly disabled on the command line or board config. -MICROPY_PY_BLUETOOTH ?= 1 -ifeq ($(MICROPY_PY_BLUETOOTH),1) -SDKCONFIG += boards/sdkconfig.ble - -# Use NimBLE on ESP32. -MICROPY_BLUETOOTH_NIMBLE ?= 1 -# Use Nimble bindings, but ESP32 IDF provides the Nimble library. -MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY = 1 -include $(TOP)/extmod/nimble/nimble.mk -endif - -# include sdkconfig to get needed configuration values -include $(SDKCONFIG) - -################################################################################ -# Compiler and linker flags - -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 -INC_ESPCOMP += -I$(ESPCOMP)/bootloader_support/include_bootloader -INC_ESPCOMP += -I$(ESPCOMP)/console -INC_ESPCOMP += -I$(ESPCOMP)/driver/include -INC_ESPCOMP += -I$(ESPCOMP)/driver/include/driver -INC_ESPCOMP += -I$(ESPCOMP)/efuse/include -INC_ESPCOMP += -I$(ESPCOMP)/efuse/esp32/include -INC_ESPCOMP += -I$(ESPCOMP)/esp32/include -INC_ESPCOMP += -I$(ESPCOMP)/espcoredump/include -INC_ESPCOMP += -I$(ESPCOMP)/soc/include -INC_ESPCOMP += -I$(ESPCOMP)/soc/esp32/include -INC_ESPCOMP += -I$(ESPCOMP)/heap/include -INC_ESPCOMP += -I$(ESPCOMP)/log/include -INC_ESPCOMP += -I$(ESPCOMP)/nvs_flash/include -INC_ESPCOMP += -I$(ESPCOMP)/freertos/include -INC_ESPCOMP += -I$(ESPCOMP)/esp_ringbuf/include -INC_ESPCOMP += -I$(ESPCOMP)/esp_event/include -INC_ESPCOMP += -I$(ESPCOMP)/tcpip_adapter/include -INC_ESPCOMP += -I$(ESPCOMP)/lwip/lwip/src/include -INC_ESPCOMP += -I$(ESPCOMP)/lwip/port/esp32/include -INC_ESPCOMP += -I$(ESPCOMP)/lwip/include/apps -INC_ESPCOMP += -I$(ESPCOMP)/lwip/include/apps/sntp -INC_ESPCOMP += -I$(ESPCOMP)/mbedtls/mbedtls/include -INC_ESPCOMP += -I$(ESPCOMP)/mbedtls/port/include -INC_ESPCOMP += -I$(ESPCOMP)/mdns/include -INC_ESPCOMP += -I$(ESPCOMP)/mdns/private_include -INC_ESPCOMP += -I$(ESPCOMP)/spi_flash/include -INC_ESPCOMP += -I$(ESPCOMP)/ulp/include -INC_ESPCOMP += -I$(ESPCOMP)/vfs/include -INC_ESPCOMP += -I$(ESPCOMP)/xtensa-debug-module/include -INC_ESPCOMP += -I$(ESPCOMP)/wpa_supplicant/include -INC_ESPCOMP += -I$(ESPCOMP)/wpa_supplicant/port/include -INC_ESPCOMP += -I$(ESPCOMP)/app_trace/include -INC_ESPCOMP += -I$(ESPCOMP)/app_update/include -INC_ESPCOMP += -I$(ESPCOMP)/pthread/include -INC_ESPCOMP += -I$(ESPCOMP)/smartconfig_ack/include -INC_ESPCOMP += -I$(ESPCOMP)/sdmmc/include - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -INC_ESPCOMP += -I$(ESPCOMP)/esp_common/include -INC_ESPCOMP += -I$(ESPCOMP)/esp_eth/include -INC_ESPCOMP += -I$(ESPCOMP)/esp_event/private_include -INC_ESPCOMP += -I$(ESPCOMP)/esp_rom/include -INC_ESPCOMP += -I$(ESPCOMP)/esp_wifi/include -INC_ESPCOMP += -I$(ESPCOMP)/esp_wifi/esp32/include -INC_ESPCOMP += -I$(ESPCOMP)/lwip/include/apps/sntp -INC_ESPCOMP += -I$(ESPCOMP)/spi_flash/private_include -INC_ESPCOMP += -I$(ESPCOMP)/wpa_supplicant/include/esp_supplicant -INC_ESPCOMP += -I$(ESPCOMP)/xtensa/include -INC_ESPCOMP += -I$(ESPCOMP)/xtensa/esp32/include -ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y) -INC_ESPCOMP += -I$(ESPCOMP)/bt/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/common/osi/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/common/btc/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/common/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/port/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ans/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/bas/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gap/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gatt/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ias/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/lls/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/tps/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/util/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/ram/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/ext/tinycrypt/include -INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/esp-hci/include -endif -else -INC_ESPCOMP += -I$(ESPCOMP)/ethernet/include -INC_ESPCOMP += -I$(ESPCOMP)/expat/expat/expat/lib -INC_ESPCOMP += -I$(ESPCOMP)/expat/port/include -INC_ESPCOMP += -I$(ESPCOMP)/json/include -INC_ESPCOMP += -I$(ESPCOMP)/json/port/include -INC_ESPCOMP += -I$(ESPCOMP)/micro-ecc/micro-ecc -INC_ESPCOMP += -I$(ESPCOMP)/nghttp/port/include -INC_ESPCOMP += -I$(ESPCOMP)/nghttp/nghttp2/lib/includes -ifeq ($(CONFIG_NIMBLE_ENABLED),y) -INC_ESPCOMP += -I$(ESPCOMP)/bt/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/porting/nimble/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/port/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/services/ans/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/services/bas/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/services/gap/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/services/gatt/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/services/ias/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/services/lls/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/services/tps/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/util/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/store/ram/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/nimble/host/store/config/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/porting/npl/freertos/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/nimble/ext/tinycrypt/include -INC_ESPCOMP += -I$(ESPCOMP)/nimble/esp-hci/include -endif -endif - -INC_NEWLIB += -I$(ESPCOMP)/newlib/platform_include -INC_NEWLIB += -I$(ESPCOMP)/newlib/include - -ifeq ($(MICROPY_PY_BLUETOOTH),1) -CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1 -CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 -endif - -# these flags are common to C and C++ compilation -CFLAGS_COMMON = -Os -ffunction-sections -fdata-sections -fstrict-volatile-bitfields \ - -mlongcalls -nostdlib \ - -Wall -Werror -Wno-error=unused-function -Wno-error=unused-but-set-variable \ - -Wno-error=unused-variable -Wno-error=deprecated-declarations \ - -DESP_PLATFORM - -CFLAGS_BASE = -std=gnu99 $(CFLAGS_COMMON) -DMBEDTLS_CONFIG_FILE='"mbedtls/esp_config.h"' -DHAVE_CONFIG_H -CFLAGS = $(CFLAGS_BASE) $(INC) $(INC_ESPCOMP) $(INC_NEWLIB) -CFLAGS += -DIDF_VER=\"$(IDF_VER)\" -CFLAGS += $(CFLAGS_MOD) $(CFLAGS_EXTRA) -CFLAGS += -I$(BOARD_DIR) - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -CFLAGS += -DMICROPY_ESP_IDF_4=1 -endif - -# this is what ESPIDF uses for c++ compilation -CXXFLAGS = -std=gnu++11 $(CFLAGS_COMMON) $(INC) $(INC_ESPCOMP) - -LDFLAGS = -nostdlib -Map=$(@:.elf=.map) --cref -LDFLAGS += --gc-sections -static -EL -LDFLAGS += -u call_user_start_cpu0 -u uxTopUsedPriority -u ld_include_panic_highint_hdl -LDFLAGS += -u __cxa_guard_dummy # so that implementation of static guards is taken from cxx_guards.o instead of libstdc++.a -LDFLAGS += -L$(ESPCOMP)/esp32/ld -LDFLAGS += -L$(ESPCOMP)/esp_rom/esp32/ld -LDFLAGS += -T $(BUILD)/esp32_out.ld -LDFLAGS += -T $(BUILD)/esp32.project.ld -LDFLAGS += -T esp32.rom.ld -LDFLAGS += -T esp32.rom.libgcc.ld -LDFLAGS += -T esp32.peripherals.ld - -LIBGCC_FILE_NAME = $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) -LIBSTDCXX_FILE_NAME = $(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a) - -# Debugging/Optimization -ifeq ($(DEBUG), 1) -CFLAGS += -g -COPT = -O0 -else -#CFLAGS += -fdata-sections -ffunction-sections -COPT += -Os -DNDEBUG -#LDFLAGS += --gc-sections -endif - -# Options for mpy-cross -MPY_CROSS_FLAGS += -march=xtensawin - -# Enable SPIRAM support if CONFIG_ESP32_SPIRAM_SUPPORT=y in sdkconfig -ifeq ($(CONFIG_ESP32_SPIRAM_SUPPORT),y) -CFLAGS_COMMON += -mfix-esp32-psram-cache-issue -LIBC_LIBM = $(ESPCOMP)/newlib/lib/libc-psram-workaround.a $(ESPCOMP)/newlib/lib/libm-psram-workaround.a -else -# Additional newlib symbols that can only be used with spiram disabled. -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -LDFLAGS += -T esp32.rom.newlib-funcs.ld -LDFLAGS += -T esp32.rom.newlib-locale.ld -LDFLAGS += -T esp32.rom.newlib-data.ld -else -LDFLAGS += -T esp32.rom.spiram_incompatible_fns.ld -endif -LIBC_LIBM = $(ESPCOMP)/newlib/lib/libc.a $(ESPCOMP)/newlib/lib/libm.a -endif - -################################################################################ -# List of MicroPython source and object files - -SRC_C = \ - main.c \ - uart.c \ - gccollect.c \ - mphalport.c \ - fatfs_port.c \ - help.c \ - modutime.c \ - moduos.c \ - machine_timer.c \ - machine_pin.c \ - machine_touchpad.c \ - machine_adc.c \ - machine_dac.c \ - machine_i2c.c \ - machine_pwm.c \ - machine_uart.c \ - modmachine.c \ - modnetwork.c \ - network_lan.c \ - network_ppp.c \ - mpnimbleport.c \ - modsocket.c \ - modesp.c \ - esp32_partition.c \ - esp32_rmt.c \ - esp32_ulp.c \ - modesp32.c \ - espneopixel.c \ - machine_hw_spi.c \ - machine_wdt.c \ - mpthreadport.c \ - machine_rtc.c \ - machine_sdcard.c \ - $(wildcard $(BOARD_DIR)/*.c) \ - $(SRC_MOD) - -EXTMOD_SRC_C += $(addprefix extmod/,\ - modonewire.c \ - ) - -LIB_SRC_C = $(addprefix lib/,\ - mbedtls_errors/mp_mbedtls_errors.c \ - mp-readline/readline.c \ - netutils/netutils.c \ - timeutils/timeutils.c \ - utils/pyexec.c \ - utils/interrupt_char.c \ - utils/sys_stdio_mphal.c \ - ) - -DRIVERS_SRC_C = $(addprefix drivers/,\ - bus/softspi.c \ - dht/dht.c \ - ) - -OBJ_MP = -OBJ_MP += $(PY_O) -OBJ_MP += $(addprefix $(BUILD)/, $(SRC_C:.c=.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)) - -# Only enable this for the MicroPython source: ignore warnings from esp-idf. -$(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) -# Append any auto-generated sources that are needed by sources listed in SRC_QSTR -SRC_QSTR_AUTO_DEPS += - -################################################################################ -# Generate sdkconfig.h from sdkconfig - -$(SDKCONFIG_COMBINED): $(SDKCONFIG) - $(Q)$(MKDIR) -p $(dir $@) - $(Q)$(CAT) $^ > $@ - -$(SDKCONFIG_H): $(SDKCONFIG_COMBINED) - $(ECHO) "GEN $@" - $(Q)$(MKDIR) -p $(dir $@) - $(Q)$(PYTHON) $(ESPIDF)/tools/kconfig_new/confgen.py \ - --output header $@ \ - --config $< \ - --kconfig $(ESPIDF)/Kconfig \ - --env "IDF_TARGET=esp32" \ - --env "IDF_CMAKE=n" \ - --env "COMPONENT_KCONFIGS=$(ESPCOMP_KCONFIGS)" \ - --env "COMPONENT_KCONFIGS_PROJBUILD=$(ESPCOMP_KCONFIGS_PROJBUILD)" \ - --env "IDF_PATH=$(ESPIDF)" - $(Q)touch $@ - -$(HEADER_BUILD)/qstrdefs.generated.h: $(SDKCONFIG_H) $(BOARD_DIR)/mpconfigboard.h - -################################################################################ -# List of object files from the ESP32 IDF components - -ESPIDF_BOOTLOADER_SUPPORT_O = $(patsubst %.c,%.o,\ - $(filter-out $(ESPCOMP)/bootloader_support/src/bootloader_init.c,\ - $(wildcard $(ESPCOMP)/bootloader_support/src/*.c) \ - $(wildcard $(ESPCOMP)/bootloader_support/src/idf/*.c) \ - )) - -ESPIDF_DRIVER_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/driver/*.c)) - -ESPIDF_EFUSE_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/efuse/esp32/*.c)\ - $(wildcard $(ESPCOMP)/efuse/src/*.c)\ - ) - -$(BUILD)/$(ESPCOMP)/esp32/dport_access.o: CFLAGS += -Wno-array-bounds -ESPIDF_ESP32_O = \ - $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp32/*.c)) \ - $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp32/hwcrypto/*.c)) \ - $(patsubst %.S,%.o,$(wildcard $(ESPCOMP)/esp32/*.S)) \ - -ESPIDF_ESP_RINGBUF_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_ringbuf/*.c)) - -ESPIDF_HEAP_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/heap/*.c)) - -ESPIDF_SOC_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/soc/esp32/*.c) \ - $(wildcard $(ESPCOMP)/soc/src/*.c) \ - $(wildcard $(ESPCOMP)/soc/src/hal/*.c) \ - ) - -$(BUILD)/$(ESPCOMP)/cxx/cxx_guards.o: CXXFLAGS += -Wno-error=sign-compare -ESPIDF_CXX_O = $(patsubst %.cpp,%.o,$(wildcard $(ESPCOMP)/cxx/*.cpp)) - -ESPIDF_PTHREAD_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/pthread/*.c)) - -# Assembler .S files need only basic flags, and in particular should not have -# -Os because that generates subtly different code. -# We also need custom CFLAGS for .c files because FreeRTOS has headers with -# generic names (eg queue.h) which can clash with other files in the port. -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -CFLAGS_ASM = -I$(BUILD) -I$(ESPCOMP)/esp32/include -I$(ESPCOMP)/soc/esp32/include -I$(ESPCOMP)/freertos/include/freertos -I. -I$(ESPCOMP)/xtensa/include -I$(ESPCOMP)/xtensa/esp32/include -I$(ESPCOMP)/esp_common/include -else -CFLAGS_ASM = -I$(BUILD) -I$(ESPCOMP)/esp32/include -I$(ESPCOMP)/soc/esp32/include -I$(ESPCOMP)/freertos/include/freertos -I. -endif -$(BUILD)/$(ESPCOMP)/freertos/portasm.o: CFLAGS = $(CFLAGS_ASM) -$(BUILD)/$(ESPCOMP)/freertos/xtensa_context.o: CFLAGS = $(CFLAGS_ASM) -$(BUILD)/$(ESPCOMP)/freertos/xtensa_intr_asm.o: CFLAGS = $(CFLAGS_ASM) -$(BUILD)/$(ESPCOMP)/freertos/xtensa_vectors.o: CFLAGS = $(CFLAGS_ASM) -$(BUILD)/$(ESPCOMP)/freertos/xtensa_vector_defaults.o: CFLAGS = $(CFLAGS_ASM) -$(BUILD)/$(ESPCOMP)/freertos/%.o: CFLAGS = $(CFLAGS_BASE) -I. -I$(BUILD) $(INC_ESPCOMP) $(INC_NEWLIB) -I$(ESPCOMP)/freertos/include/freertos -D_ESP_FREERTOS_INTERNAL -ESPIDF_FREERTOS_O = \ - $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/freertos/*.c)) \ - $(patsubst %.S,%.o,$(wildcard $(ESPCOMP)/freertos/*.S)) \ - -ESPIDF_VFS_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/vfs/*.c)) - -ESPIDF_LOG_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/log/*.c)) - -ESPIDF_XTENSA_DEBUG_MODULE_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/xtensa-debug-module/*.c)) - -ESPIDF_TCPIP_ADAPTER_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/tcpip_adapter/*.c)) - -ESPIDF_APP_TRACE_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/app_trace/*.c)) - -ESPIDF_APP_UPDATE_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/app_update/*.c)) -ESPIDF_NEWLIB_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/newlib/*.c)) +PYTHON ?= python3 -$(BUILD)/$(ESPCOMP)/nvs_flash/src/nvs_api.o: CXXFLAGS += -Wno-error=sign-compare -ESPIDF_NVS_FLASH_O = $(patsubst %.cpp,%.o,$(wildcard $(ESPCOMP)/nvs_flash/src/*.cpp)) - -ESPIDF_SMARTCONFIG_ACK_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/smartconfig_ack/*.c)) - -ESPIDF_SPI_FLASH_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/spi_flash/*.c)) - -ESPIDF_ULP_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/ulp/*.c)) - -$(BUILD)/$(ESPCOMP)/lwip/%.o: CFLAGS += -Wno-address -Wno-unused-variable -Wno-unused-but-set-variable -ESPIDF_LWIP_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/lwip/apps/dhcpserver/*.c) \ - $(wildcard $(ESPCOMP)/lwip/lwip/src/api/*.c) \ - $(wildcard $(ESPCOMP)/lwip/lwip/src/apps/sntp/*.c) \ - $(wildcard $(ESPCOMP)/lwip/lwip/src/core/*.c) \ - $(wildcard $(ESPCOMP)/lwip/lwip/src/core/*/*.c) \ - $(wildcard $(ESPCOMP)/lwip/lwip/src/netif/*.c) \ - $(wildcard $(ESPCOMP)/lwip/lwip/src/netif/*/*.c) \ - $(wildcard $(ESPCOMP)/lwip/lwip/src/netif/*/*/*.c) \ - $(wildcard $(ESPCOMP)/lwip/port/esp32/*.c) \ - $(wildcard $(ESPCOMP)/lwip/port/esp32/*/*.c) \ - ) - -# Mbedtls source files, exclude error.c in favor of lib/mbedtls_errors/mp_mbedtls_errors.c -ESPIDF_MBEDTLS_O = $(patsubst %.c,%.o, $(filter-out %/error.c,\ - $(wildcard $(ESPCOMP)/mbedtls/mbedtls/library/*.c) \ - $(wildcard $(ESPCOMP)/mbedtls/port/*.c) \ - $(wildcard $(ESPCOMP)/mbedtls/port/esp32/*.c) \ - )) - -ESPIDF_MDNS_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/mdns/*.c)) - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -$(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DCONFIG_WPA3_SAE -DCONFIG_IEEE80211W -DESP_SUPPLICANT -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_TLS -DEAP_TTLS -DEAP_PEAP -DEAP_MSCHAPv2 -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DCONFIG_ECC -D__ets__ -Wno-strict-aliasing -I$(ESPCOMP)/wpa_supplicant/src -Wno-implicit-function-declaration -else -$(BUILD)/$(ESPCOMP)/wpa_supplicant/%.o: CFLAGS += -DEMBEDDED_SUPP -DIEEE8021X_EAPOL -DEAP_PEER_METHOD -DEAP_MSCHAPv2 -DEAP_TTLS -DEAP_TLS -DEAP_PEAP -DUSE_WPA2_TASK -DCONFIG_WPS2 -DCONFIG_WPS_PIN -DUSE_WPS_TASK -DESPRESSIF_USE -DESP32_WORKAROUND -DALLOW_EVEN_MOD -D__ets__ -Wno-strict-aliasing -endif -ESPIDF_WPA_SUPPLICANT_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/wpa_supplicant/port/*.c) \ - $(wildcard $(ESPCOMP)/wpa_supplicant/src/*/*.c) \ - ) - -ESPIDF_SDMMC_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/sdmmc/*.c)) - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -ESPIDF_ESP_COMMON_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_common/src/*.c)) - -ESPIDF_ESP_EVENT_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_event/*.c)) - -ESPIDF_ESP_WIFI_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_wifi/src/*.c)) - -ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y) -ESPIDF_BT_NIMBLE_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/bt/controller/*.c) \ - $(wildcard $(ESPCOMP)/bt/common/btc/core/*.c) \ - $(wildcard $(ESPCOMP)/bt/common/osi/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/esp-hci/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/ext/tinycrypt/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ans/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/bas/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gap/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gatt/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ias/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/lls/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/tps/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/src/ble_store_config.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/src/ble_store_nvs.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/ram/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/util/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/src/*.c) \ - $(wildcard $(ESPCOMP)/bt/host/nimble/port/src/*.c) \ - ) -endif - -$(BUILD)/$(ESPCOMP)/esp_eth/src/esp_eth_mac_dm9051.o: CFLAGS += -fno-strict-aliasing -ESPIDF_ESP_ETH_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_eth/src/*.c)) - -ESPIDF_XTENSA_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/xtensa/*.c) \ - $(wildcard $(ESPCOMP)/xtensa/esp32/*.c) \ - ) -else -ESPIDF_JSON_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/json/cJSON/cJSON*.c)) - -ESPIDF_ETHERNET_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/ethernet/*.c) \ - $(wildcard $(ESPCOMP)/ethernet/eth_phy/*.c) \ - ) - -ifeq ($(CONFIG_NIMBLE_ENABLED),y) -ESPIDF_BT_NIMBLE_O = $(patsubst %.c,%.o,\ - $(wildcard $(ESPCOMP)/bt/*.c) \ - $(wildcard $(ESPCOMP)/nimble/esp-hci/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/ext/tinycrypt/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/services/ans/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/services/bas/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/services/gap/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/services/gatt/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/services/ias/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/services/lls/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/services/tps/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/store/config/src/ble_store_config.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/store/config/src/ble_store_nvs.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/store/ram/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/host/util/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/nimble/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/porting/nimble/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/nimble/porting/npl/freertos/src/*.c) \ - $(wildcard $(ESPCOMP)/nimble/port/src/*.c) \ - ) -endif -endif - -OBJ_ESPIDF = -LIB_ESPIDF = -BUILD_ESPIDF_LIB = $(BUILD)/esp-idf - -define gen_espidf_lib_rule -OBJ_ESPIDF += $(addprefix $$(BUILD)/,$(2)) -LIB_ESPIDF += $(1) -$(BUILD_ESPIDF_LIB)/$(1)/lib$(1).a: $(addprefix $$(BUILD)/,$(2)) - $(ECHO) "AR $$@" - $(Q)$(AR) cru $$@ $$^ -endef - -$(eval $(call gen_espidf_lib_rule,bootloader_support,$(ESPIDF_BOOTLOADER_SUPPORT_O))) -$(eval $(call gen_espidf_lib_rule,driver,$(ESPIDF_DRIVER_O))) -$(eval $(call gen_espidf_lib_rule,efuse,$(ESPIDF_EFUSE_O))) -$(eval $(call gen_espidf_lib_rule,esp32,$(ESPIDF_ESP32_O))) -$(eval $(call gen_espidf_lib_rule,esp_ringbuf,$(ESPIDF_ESP_RINGBUF_O))) -$(eval $(call gen_espidf_lib_rule,heap,$(ESPIDF_HEAP_O))) -$(eval $(call gen_espidf_lib_rule,soc,$(ESPIDF_SOC_O))) -$(eval $(call gen_espidf_lib_rule,cxx,$(ESPIDF_CXX_O))) -$(eval $(call gen_espidf_lib_rule,pthread,$(ESPIDF_PTHREAD_O))) -$(eval $(call gen_espidf_lib_rule,freertos,$(ESPIDF_FREERTOS_O))) -$(eval $(call gen_espidf_lib_rule,vfs,$(ESPIDF_VFS_O))) -$(eval $(call gen_espidf_lib_rule,json,$(ESPIDF_JSON_O))) -$(eval $(call gen_espidf_lib_rule,log,$(ESPIDF_LOG_O))) -$(eval $(call gen_espidf_lib_rule,xtensa-debug-module,$(ESPIDF_XTENSA_DEBUG_MODULE_O))) -$(eval $(call gen_espidf_lib_rule,tcpip_adapter,$(ESPIDF_TCPIP_ADAPTER_O))) -$(eval $(call gen_espidf_lib_rule,app_trace,$(ESPIDF_APP_TRACE_O))) -$(eval $(call gen_espidf_lib_rule,app_update,$(ESPIDF_APP_UPDATE_O))) -$(eval $(call gen_espidf_lib_rule,newlib,$(ESPIDF_NEWLIB_O))) -$(eval $(call gen_espidf_lib_rule,nvs_flash,$(ESPIDF_NVS_FLASH_O))) -$(eval $(call gen_espidf_lib_rule,smartconfig_ack,$(ESPIDF_SMARTCONFIG_ACK_O))) -$(eval $(call gen_espidf_lib_rule,spi_flash,$(ESPIDF_SPI_FLASH_O))) -$(eval $(call gen_espidf_lib_rule,ulp,$(ESPIDF_ULP_O))) -$(eval $(call gen_espidf_lib_rule,lwip,$(ESPIDF_LWIP_O))) -$(eval $(call gen_espidf_lib_rule,mbedtls,$(ESPIDF_MBEDTLS_O))) -$(eval $(call gen_espidf_lib_rule,mdns,$(ESPIDF_MDNS_O))) -$(eval $(call gen_espidf_lib_rule,wpa_supplicant,$(ESPIDF_WPA_SUPPLICANT_O))) -$(eval $(call gen_espidf_lib_rule,sdmmc,$(ESPIDF_SDMMC_O))) -$(eval $(call gen_espidf_lib_rule,bt_nimble,$(ESPIDF_BT_NIMBLE_O))) - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -$(eval $(call gen_espidf_lib_rule,esp_common,$(ESPIDF_ESP_COMMON_O))) -$(eval $(call gen_espidf_lib_rule,esp_event,$(ESPIDF_ESP_EVENT_O))) -$(eval $(call gen_espidf_lib_rule,esp_wifi,$(ESPIDF_ESP_WIFI_O))) -$(eval $(call gen_espidf_lib_rule,esp_eth,$(ESPIDF_ESP_ETH_O))) -$(eval $(call gen_espidf_lib_rule,xtensa,$(ESPIDF_XTENSA_O))) -else -$(eval $(call gen_espidf_lib_rule,ethernet,$(ESPIDF_ETHERNET_O))) -endif - -# Create all destination build dirs before compiling IDF source -OBJ_ESPIDF_DIRS = $(sort $(dir $(OBJ_ESPIDF))) $(BUILD_ESPIDF_LIB) $(addprefix $(BUILD_ESPIDF_LIB)/,$(LIB_ESPIDF)) -$(OBJ_ESPIDF): | $(OBJ_ESPIDF_DIRS) -$(OBJ_ESPIDF_DIRS): - $(MKDIR) -p $@ - -# Make all IDF object files depend on sdkconfig -$(OBJ_ESPIDF): $(SDKCONFIG_H) - -# Add all IDF components to the set of libraries -LIB = $(foreach lib,$(LIB_ESPIDF),$(BUILD_ESPIDF_LIB)/$(lib)/lib$(lib).a) - -################################################################################ -# ESP IDF ldgen - -LDGEN_FRAGMENTS = $(shell find $(ESPCOMP) -name "*.lf") - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) - -LDGEN_LIBRARIES=$(foreach lib,$(LIB_ESPIDF),$(BUILD_ESPIDF_LIB)/$(lib)/lib$(lib).a) - -$(BUILD_ESPIDF_LIB)/ldgen_libraries: $(LDGEN_LIBRARIES) $(ESPIDF)/make/ldgen.mk - printf "$(foreach library,$(LDGEN_LIBRARIES),$(library)\n)" > $(BUILD_ESPIDF_LIB)/ldgen_libraries - -$(BUILD)/esp32.project.ld: $(ESPCOMP)/esp32/ld/esp32.project.ld.in $(LDGEN_FRAGMENTS) $(SDKCONFIG_COMBINED) $(BUILD_ESPIDF_LIB)/ldgen_libraries - $(ECHO) "GEN $@" - $(Q)$(PYTHON) $(ESPIDF)/tools/ldgen/ldgen.py \ - --input $< \ - --output $@ \ - --config $(SDKCONFIG_COMBINED) \ - --kconfig $(ESPIDF)/Kconfig \ - --fragments $(LDGEN_FRAGMENTS) \ - --libraries-file $(BUILD_ESPIDF_LIB)/ldgen_libraries \ - --env "IDF_TARGET=esp32" \ - --env "IDF_CMAKE=n" \ - --env "COMPONENT_KCONFIGS=$(ESPCOMP_KCONFIGS)" \ - --env "COMPONENT_KCONFIGS_PROJBUILD=$(ESPCOMP_KCONFIGS_PROJBUILD)" \ - --env "IDF_PATH=$(ESPIDF)" \ - --objdump $(OBJDUMP) - -else - -LDGEN_SECTIONS_INFO = $(foreach lib,$(LIB_ESPIDF),$(BUILD_ESPIDF_LIB)/$(lib)/lib$(lib).a.sections_info) -LDGEN_SECTION_INFOS = $(BUILD_ESPIDF_LIB)/ldgen.section_infos - -define gen_sections_info_rule -$(1).sections_info: $(1) - $(ECHO) "GEN $(1).sections_info" - $(Q)$(OBJDUMP) -h $(1) > $(1).sections_info -endef - -$(eval $(foreach lib,$(LIB_ESPIDF),$(eval $(call gen_sections_info_rule,$(BUILD_ESPIDF_LIB)/$(lib)/lib$(lib).a)))) - -$(LDGEN_SECTION_INFOS): $(LDGEN_SECTIONS_INFO) $(ESPIDF)/make/ldgen.mk - $(Q)printf "$(foreach info,$(LDGEN_SECTIONS_INFO),$(info)\n)" > $@ - -$(BUILD)/esp32.project.ld: $(ESPCOMP)/esp32/ld/esp32.project.ld.in $(LDGEN_FRAGMENTS) $(SDKCONFIG_COMBINED) $(LDGEN_SECTION_INFOS) - $(ECHO) "GEN $@" - $(Q)$(PYTHON) $(ESPIDF)/tools/ldgen/ldgen.py \ - --input $< \ - --output $@ \ - --config $(SDKCONFIG_COMBINED) \ - --kconfig $(ESPIDF)/Kconfig \ - --fragments $(LDGEN_FRAGMENTS) \ - --sections $(LDGEN_SECTION_INFOS) \ - --env "IDF_TARGET=esp32" \ - --env "IDF_CMAKE=n" \ - --env "COMPONENT_KCONFIGS=$(ESPCOMP_KCONFIGS)" \ - --env "COMPONENT_KCONFIGS_PROJBUILD=$(ESPCOMP_KCONFIGS_PROJBUILD)" \ - --env "IDF_PATH=$(ESPIDF)" - -endif +GIT_SUBMODULES = lib/berkeley-db-1.xx -################################################################################ -# Main targets +.PHONY: all clean deploy erase submodules FORCE -all: $(BUILD)/firmware.bin +IDFPY_FLAGS += -D MICROPY_BOARD=$(BOARD) -B $(BUILD) -.PHONY: idf-version deploy erase +all: + idf.py $(IDFPY_FLAGS) build + @$(PYTHON) makeimg.py \ + $(BUILD)/bootloader/bootloader.bin \ + $(BUILD)/partition_table/partition-table.bin \ + $(BUILD)/micropython.bin \ + $(BUILD)/firmware.bin -idf-version: - $(ECHO) "ESP IDF supported hash: $(ESPIDF_SUPHASH)" +$(BUILD)/bootloader/bootloader.bin $(BUILD)/partition_table/partition -table.bin $(BUILD)/micropython.bin: FORCE -$(BUILD)/firmware.bin: $(BUILD)/bootloader.bin $(BUILD)/partitions.bin $(BUILD)/application.bin - $(ECHO) "Create $@" - $(Q)$(PYTHON) makeimg.py $^ $@ +clean: + idf.py $(IDFPY_FLAGS) fullclean -deploy: $(BUILD)/firmware.bin - $(ECHO) "Writing $^ to the board" - $(Q)$(ESPTOOL) --chip esp32 --port $(PORT) --baud $(BAUD) write_flash -z --flash_mode $(FLASH_MODE) --flash_freq $(FLASH_FREQ) 0x1000 $^ +deploy: + idf.py $(IDFPY_FLAGS) -p $(PORT) -b $(BAUD) flash erase: - $(ECHO) "Erasing flash" - $(Q)$(ESPTOOL) --chip esp32 --port $(PORT) --baud $(BAUD) erase_flash - -################################################################################ -# Declarations to build the application - -OBJ = $(OBJ_MP) - -APP_LD_ARGS = -APP_LD_ARGS += $(LDFLAGS_MOD) -APP_LD_ARGS += $(addprefix -T,$(LD_FILES)) -APP_LD_ARGS += --start-group -APP_LD_ARGS += -L$(dir $(LIBGCC_FILE_NAME)) -lgcc -APP_LD_ARGS += -L$(dir $(LIBSTDCXX_FILE_NAME)) -lstdc++ -APP_LD_ARGS += $(LIBC_LIBM) -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -APP_LD_ARGS += -L$(ESPCOMP)/xtensa/esp32 -lhal -APP_LD_ARGS += -L$(ESPCOMP)/bt/controller/lib -lbtdm_app -APP_LD_ARGS += -L$(ESPCOMP)/esp_wifi/lib_esp32 -lcore -lmesh -lnet80211 -lphy -lrtc -lpp -lsmartconfig -lcoexist -else -APP_LD_ARGS += $(ESPCOMP)/esp32/libhal.a -APP_LD_ARGS += -L$(ESPCOMP)/bt/lib -lbtdm_app -APP_LD_ARGS += -L$(ESPCOMP)/esp32/lib -lcore -lmesh -lnet80211 -lphy -lrtc -lpp -lwpa -lsmartconfig -lcoexist -lwps -lwpa2 -endif -APP_LD_ARGS += $(OBJ) -APP_LD_ARGS += $(LIB) -APP_LD_ARGS += --end-group - -$(BUILD)/esp32_out.ld: $(SDKCONFIG_H) - $(Q)$(CC) -I$(BUILD) -C -P -x c -E $(ESPCOMP)/esp32/ld/esp32.ld -o $@ - -$(BUILD)/application.bin: $(BUILD)/application.elf - $(ECHO) "Create $@" - $(Q)$(ESPTOOL) --chip esp32 elf2image --flash_mode $(FLASH_MODE) --flash_freq $(FLASH_FREQ) --flash_size $(FLASH_SIZE) $< - -$(BUILD)/application.elf: $(OBJ) $(LIB) $(BUILD)/esp32_out.ld $(BUILD)/esp32.project.ld - $(ECHO) "LINK $@" - $(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 - -BOOTLOADER_LIB_DIR = $(BUILD)/bootloader -BOOTLOADER_LIB_ALL = - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -$(BUILD)/bootloader/$(ESPCOMP)/%.o: CFLAGS += -DBOOTLOADER_BUILD=1 -I$(ESPCOMP)/bootloader_support/include_priv -I$(ESPCOMP)/bootloader_support/include -I$(ESPCOMP)/efuse/include -I$(ESPCOMP)/esp_rom/include -Wno-error=format \ - -I$(ESPCOMP)/esp_common/include \ - -I$(ESPCOMP)/xtensa/include \ - -I$(ESPCOMP)/xtensa/esp32/include -else -$(BUILD)/bootloader/$(ESPCOMP)/%.o: CFLAGS += -DBOOTLOADER_BUILD=1 -I$(ESPCOMP)/bootloader_support/include_priv -I$(ESPCOMP)/bootloader_support/include -I$(ESPCOMP)/micro-ecc/micro-ecc -I$(ESPCOMP)/efuse/include -I$(ESPCOMP)/esp32 -Wno-error=format -endif - -# libbootloader_support.a -BOOTLOADER_LIB_ALL += bootloader_support -BOOTLOADER_LIB_BOOTLOADER_SUPPORT_OBJ = $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/,\ - bootloader_support/src/bootloader_clock.o \ - bootloader_support/src/bootloader_common.o \ - bootloader_support/src/bootloader_flash.o \ - bootloader_support/src/bootloader_flash_config.o \ - bootloader_support/src/bootloader_init.o \ - bootloader_support/src/bootloader_random.o \ - bootloader_support/src/bootloader_utility.o \ - bootloader_support/src/flash_qio_mode.o \ - bootloader_support/src/esp_image_format.o \ - bootloader_support/src/flash_encrypt.o \ - bootloader_support/src/flash_partitions.o \ - ) - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -BOOTLOADER_LIB_BOOTLOADER_SUPPORT_OBJ += $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/,\ - bootloader_support/src/esp32/bootloader_sha.o \ - bootloader_support/src/bootloader_flash_config.o \ - bootloader_support/src/esp32/secure_boot.o \ - ) -else -BOOTLOADER_LIB_BOOTLOADER_SUPPORT_OBJ += $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/,\ - bootloader_support/src/bootloader_sha.o \ - bootloader_support/src/secure_boot_signatures.o \ - bootloader_support/src/secure_boot.o \ - ) -endif - -$(BOOTLOADER_LIB_DIR)/libbootloader_support.a: $(BOOTLOADER_LIB_BOOTLOADER_SUPPORT_OBJ) - $(ECHO) "AR $@" - $(Q)$(AR) cr $@ $^ - -# liblog.a -BOOTLOADER_LIB_ALL += log -BOOTLOADER_LIB_LOG_OBJ = $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/,\ - log/log.o \ - ) -$(BOOTLOADER_LIB_DIR)/liblog.a: $(BOOTLOADER_LIB_LOG_OBJ) - $(ECHO) "AR $@" - $(Q)$(AR) cr $@ $^ - -# libspi_flash.a -BOOTLOADER_LIB_ALL += spi_flash -BOOTLOADER_LIB_SPI_FLASH_OBJ = $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/,\ - spi_flash/spi_flash_rom_patch.o \ - ) -$(BOOTLOADER_LIB_DIR)/libspi_flash.a: $(BOOTLOADER_LIB_SPI_FLASH_OBJ) - $(ECHO) "AR $@" - $(Q)$(AR) cr $@ $^ - -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V3)) -# libmicro-ecc.a -BOOTLOADER_LIB_ALL += micro-ecc -BOOTLOADER_LIB_MICRO_ECC_OBJ = $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/,\ - micro-ecc/micro-ecc/uECC.o \ - ) -$(BOOTLOADER_LIB_DIR)/libmicro-ecc.a: $(BOOTLOADER_LIB_MICRO_ECC_OBJ) - $(ECHO) "AR $@" - $(Q)$(AR) cr $@ $^ -endif - -# libsoc.a -$(BUILD)/bootloader/$(ESPCOMP)/soc/esp32/rtc_clk.o: CFLAGS += -fno-jump-tables -fno-tree-switch-conversion -BOOTLOADER_LIB_ALL += soc -BOOTLOADER_LIB_SOC_OBJ = $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/soc/,\ - esp32/cpu_util.o \ - esp32/gpio_periph.o \ - esp32/rtc_clk.o \ - esp32/rtc_clk_init.o \ - esp32/rtc_init.o \ - esp32/rtc_periph.o \ - esp32/rtc_pm.o \ - esp32/rtc_sleep.o \ - esp32/rtc_time.o \ - esp32/rtc_wdt.o \ - esp32/sdio_slave_periph.o \ - esp32/sdmmc_periph.o \ - esp32/soc_memory_layout.o \ - esp32/spi_periph.o \ - src/memory_layout_utils.o \ - ) -$(BOOTLOADER_LIB_DIR)/libsoc.a: $(BOOTLOADER_LIB_SOC_OBJ) - $(ECHO) "AR $@" - $(Q)$(AR) cr $@ $^ - -# libmain.a -BOOTLOADER_LIB_ALL += main -BOOTLOADER_LIB_MAIN_OBJ = $(addprefix $(BUILD)/bootloader/$(ESPCOMP)/,\ - bootloader/subproject/main/bootloader_start.o \ - ) -$(BOOTLOADER_LIB_DIR)/libmain.a: $(BOOTLOADER_LIB_MAIN_OBJ) - $(ECHO) "AR $@" - $(Q)$(AR) cr $@ $^ - -# all objects files -BOOTLOADER_OBJ_ALL = \ - $(BOOTLOADER_LIB_BOOTLOADER_SUPPORT_OBJ) \ - $(BOOTLOADER_LIB_LOG_OBJ) \ - $(BOOTLOADER_LIB_SPI_FLASH_OBJ) \ - $(BOOTLOADER_LIB_MICRO_ECC_OBJ) \ - $(BOOTLOADER_LIB_SOC_OBJ) \ - $(BOOTLOADER_LIB_MAIN_OBJ) - -$(BOOTLOADER_OBJ_ALL): $(SDKCONFIG_H) - -BOOTLOADER_LIBS = -BOOTLOADER_LIBS += -Wl,--start-group -BOOTLOADER_LIBS += $(BOOTLOADER_OBJ) -BOOTLOADER_LIBS += -L$(BUILD)/bootloader $(addprefix -l,$(BOOTLOADER_LIB_ALL)) -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -BOOTLOADER_LIBS += -L$(ESPCOMP)/esp_wifi/lib_esp32 -lrtc -else -BOOTLOADER_LIBS += -L$(ESPCOMP)/esp32/lib -lrtc -endif -BOOTLOADER_LIBS += -L$(dir $(LIBGCC_FILE_NAME)) -lgcc -BOOTLOADER_LIBS += -Wl,--end-group - -BOOTLOADER_LDFLAGS = -BOOTLOADER_LDFLAGS += -nostdlib -BOOTLOADER_LDFLAGS += -L$(ESPIDF)/lib -BOOTLOADER_LDFLAGS += -L$(ESPIDF)/ld -BOOTLOADER_LDFLAGS += -u call_user_start_cpu0 -BOOTLOADER_LDFLAGS += -Wl,--gc-sections -BOOTLOADER_LDFLAGS += -static -BOOTLOADER_LDFLAGS += -Wl,-EL -BOOTLOADER_LDFLAGS += -Wl,-Map=$(@:.elf=.map) -Wl,--cref -BOOTLOADER_LDFLAGS += -T $(ESPCOMP)/bootloader/subproject/main/esp32.bootloader.ld -BOOTLOADER_LDFLAGS += -T $(ESPCOMP)/bootloader/subproject/main/esp32.bootloader.rom.ld -ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4)) -BOOTLOADER_LDFLAGS += -T $(ESPCOMP)/esp_rom/esp32/ld/esp32.rom.ld -BOOTLOADER_LDFLAGS += -T $(ESPCOMP)/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld -else -BOOTLOADER_LDFLAGS += -T $(ESPCOMP)/esp32/ld/esp32.rom.ld -BOOTLOADER_LDFLAGS += -T $(ESPCOMP)/esp32/ld/esp32.rom.spiram_incompatible_fns.ld -endif -BOOTLOADER_LDFLAGS += -T $(ESPCOMP)/esp32/ld/esp32.peripherals.ld - -BOOTLOADER_OBJ_DIRS = $(sort $(dir $(BOOTLOADER_OBJ_ALL))) -$(BOOTLOADER_OBJ_ALL): | $(BOOTLOADER_OBJ_DIRS) -$(BOOTLOADER_OBJ_DIRS): - $(MKDIR) -p $@ - -$(BUILD)/bootloader/%.o: %.c - $(call compile_c) - -$(BUILD)/bootloader.bin: $(BUILD)/bootloader.elf - $(ECHO) "Create $@" - $(Q)$(ESPTOOL) --chip esp32 elf2image --flash_mode $(FLASH_MODE) --flash_freq $(FLASH_FREQ) --flash_size $(FLASH_SIZE) $< - -$(BUILD)/bootloader.elf: $(BOOTLOADER_OBJ) $(addprefix $(BOOTLOADER_LIB_DIR)/lib,$(addsuffix .a,$(BOOTLOADER_LIB_ALL))) - $(ECHO) "LINK $@" - $(Q)$(CC) $(BOOTLOADER_LDFLAGS) -o $@ $(BOOTLOADER_LIBS) - -################################################################################ -# Declarations to build the partitions - -PYTHON2 ?= python2 - -# Can be overriden by mkconfigboard.mk. -PART_SRC ?= partitions.csv - -$(BUILD)/partitions.bin: $(PART_SRC) - $(ECHO) "Create $@" - $(Q)$(PYTHON2) $(ESPCOMP)/partition_table/gen_esp32part.py -q $< $@ - -################################################################################ + idf.py $(IDFPY_FLAGS) erase_flash -include $(TOP)/py/mkrules.mk +submodules: + git submodule update --init $(addprefix ../../,$(GIT_SUBMODULES)) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index 54fb41cf6..c349f44b2 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -1,13 +1,13 @@ MicroPython port to the ESP32 ============================= -This is an experimental port of MicroPython to the Espressif ESP32 -microcontroller. It uses the ESP-IDF framework and MicroPython runs as +This is a port of MicroPython to the Espressif ESP32 series of +microcontrollers. It uses the ESP-IDF framework and MicroPython runs as a task under FreeRTOS. Supported features include: - REPL (Python prompt) over UART0. -- 16k stack for the MicroPython task and 96k Python heap. +- 16k stack for the MicroPython task and approximately 100k Python heap. - Many of MicroPython's features are enabled: unicode, arbitrary-precision integers, single-precision floats, complex numbers, frozen bytecode, as well as many of the internal modules. @@ -15,16 +15,24 @@ Supported features include: - The machine module with GPIO, UART, SPI, software I2C, ADC, DAC, PWM, TouchPad, WDT and Timer. - The network module with WLAN (WiFi) support. +- Bluetooth low-energy (BLE) support via the bluetooth module. -Development of this ESP32 port was sponsored in part by Microbric Pty Ltd. +Initial development of this ESP32 port was sponsored in part by Microbric Pty Ltd. -Setting up the toolchain and ESP-IDF ------------------------------------- +Setting up ESP-IDF and the build environment +-------------------------------------------- -There are two main components that are needed to build the firmware: -- the Xtensa cross-compiler that targets the CPU in the ESP32 (this is - different to the compiler used by the ESP8266) -- the Espressif IDF (IoT development framework, aka SDK) +MicroPython on ESP32 requires the Espressif IDF version 4 (IoT development +framework, aka SDK). The ESP-IDF includes the libraries and RTOS needed to +manage the ESP32 microcontroller, as well as a way to manage the required +build environment and toolchains needed to build the firmware. + +The ESP-IDF changes quickly and MicroPython only supports certain versions. +Currently MicroPython supports v4.0.2, v4.1.1 and v4.2, +although other IDF v4 versions may also work. + +To install the ESP-IDF the full instructions can be found at the +[Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/v4.0.2/get-started/index.html#installation-step-by-step). If you are on a Windows machine then the [Windows Subsystem for Linux](https://msdn.microsoft.com/en-au/commandline/wsl/install_guide) is the @@ -32,164 +40,32 @@ most efficient way to install the ESP32 toolchain and build the project. If you use WSL then follow the Linux instructions rather than the Windows instructions. -The ESP-IDF changes quickly and MicroPython only supports certain versions. -The git hash of these versions (one for 3.x, one for 4.x) can be found by -running `make` without a configured `ESPIDF`. Then you can fetch the -required IDF using the following command: - -```bash -$ cd ports/esp32 -$ make ESPIDF= # This will print the supported hashes, copy the one you want. -$ export ESPIDF=$HOME/src/github.com/espressif/esp-idf # Or any path you like. -$ mkdir -p $ESPIDF -$ cd $ESPIDF -$ git clone https://github.com/espressif/esp-idf.git $ESPIDF -$ git checkout -$ git submodule update --init --recursive -``` - -Note: The ESP IDF v4.x support is currently experimental. It does not -currently support PPP or wired Ethernet. - -Python dependencies -=================== - -You will also need other dependencies from the IDF, see -`$ESPIDF/requirements.txt`, but at a minimum you need `pyserial>=3.0` and -`pyparsing>=2.0.3,<2.4.0`. - -You can use Python 2 or Python 3. If you need to override the system default -add (for example) `PYTHON=python3` to any of the `make` commands below. - -It is recommended to use a Python virtual environment. Even if your system -package manager already provides these libraries, the IDF v4.x is currently -incompatible with pyparsing 2.4 and higher. - -For example, to set up a Python virtual environment from scratch: - -```bash -$ cd ports/esp32 -$ python3 -m venv build-venv -$ source build-venv/bin/activate -$ pip install --upgrade pip -$ pip install -r path/to/esp-idf/requirements.txt -``` +The Espressif instructions will guide you through using the `install.sh` +(or `install.bat`) script to download the toolchain and set up your environment. +The steps to take are summarised below. -To re-enter this virtual environment in future sessions, you only need to -source the `activate` script, i.e.: +To check out a copy of the IDF use git clone: ```bash -$ cd ports/esp32 -$ source build-venv/bin/activate +$ git clone -b v4.0.2 --recursive https://github.com/espressif/esp-idf.git ``` -Then, to install the toolchain (which includes the GCC compiler, linker, binutils, -etc), there are two options: - -1. Using the IDF scripts to install the toolchain (IDF 4.x only) -================================================================ - -Follow the steps at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/v4.0/get-started/index.html#step-3-set-up-the-tools). - -This will guide you through using the `install.sh` (or `install.bat`) script -to download the toolchain and add it to your `PATH`. The steps are summarised -below: - -After you've cloned and checked out the IDF to the correct version (see -above), run the `install.sh` script: - -```bash -$ cd $ESPIDF -$ ./install.sh # (or install.bat on Windows) -``` +You can replace `v4.0.2` with `v4.1.1` or any other supported version. +(You don't need a full recursive clone; see the `ci_esp32_setup` function in +`tools/ci.sh` in this repository for more detailed set-up commands.) -Then in the `ports/esp32` directory, source the `export.sh` script to set the -`PATH`. +After you've cloned and checked out the IDF to the correct version, run the +`install.sh` script: ```bash -$ cd micropython/ports/esp32 -$ source $ESPIDF/export.sh # (or path\to\esp-idf\export.bat on Windows) -$ # Run make etc, see below. +$ cd esp-idf +$ ./install.sh # (or install.bat on Windows) +$ source export.sh # (or export.bat on Windows) ``` The `install.sh` step only needs to be done once. You will need to source `export.sh` for every new session. -Note: If you get an error about `--no-site-packages`, then modify -`$ESPIDF/tools/idf_tools.py` and make the same change as [this -commit](https://github.com/espressif/esp-idf/commit/7a18f02acd7005f7c56e62175a8d1968a1a9019d). - -2. or, Downloading pre-built toolchain manually (IDF 3.x and 4.x) -============================================================= - -Note: while this works with 4.x, if you're using the 4.x IDF, it's much -simpler to use the guide above, which will also get a more recent version of -the toolchain. - -You can follow the 3.x guide at: - - * [Linux installation](https://docs.espressif.com/projects/esp-idf/en/v3.3.2/get-started/linux-setup.html) - * [MacOS installation](https://docs.espressif.com/projects/esp-idf/en/v3.3.2/get-started/macos-setup.html) - * [Windows installation](https://docs.espressif.com/projects/esp-idf/en/v3.3.2/get-started/windows-setup.html) - -You will need to update your `PATH` environment variable to include the ESP32 -toolchain. For example, you can issue the following commands on (at least) -Linux: - - $ export PATH=$PATH:$HOME/esp/crosstool-NG/builds/xtensa-esp32-elf/bin - -You can put this command in your `.profile` or `.bash_login`, or do it manually. - -Configuring the MicroPython build ---------------------------------- - -You then need to set the `ESPIDF` environment/makefile variable to point to -the root of the ESP-IDF repository. The recommended way to do this is to have -a custom `makefile` in `ports/esp32` which sets any additional variables, then -includes the main `Makefile`. Note that GNU Make will preferentially run -`GNUmakefile`, then `makefile`, then `Makefile`, which is what allows this to -work. On case-insensitive filesystems, you'll need to use `GNUmakefile` rather -than `makefile`. - -Create a new file in the esp32 directory called `makefile` (or `GNUmakefile`) -and add the following lines to that file: - -``` -ESPIDF ?= -BOARD ?= GENERIC -#PORT ?= /dev/ttyUSB0 -#FLASH_MODE ?= qio -#FLASH_SIZE ?= 4MB -#CROSS_COMPILE ?= xtensa-esp32-elf- - -include Makefile -``` - -Be sure to enter the correct path to your local copy of the IDF repository -(and use `$(HOME)`, not tilde (`~`), to reference your home directory). - -If the Xtensa cross-compiler is not in your path you can use the -`CROSS_COMPILE` variable to set its location. Other options of interest are -`PORT` for the serial port of your ESP32 module, and `FLASH_MODE` (which may -need to be `dio` for some modules) and `FLASH_SIZE`. See the Makefile for -further information. - -The default ESP IDF configuration settings are provided by the `GENERIC` -board definition in the directory `boards/GENERIC`. For a custom configuration -you can define your own board directory. - -Any of these variables can also be set on the make command line, e.g. to set -the `BOARD` variable, use: - -```bash -$ make BOARD=TINYPICO -``` - -Note the use of `?=` in the `makefile` which allows them to be overridden on -the command line. There is also a `GENERIC_SPIRAM` board for for ESP32 -modules that have external SPIRAM, but prefer to use a specific board target -(or define your own as necessary). - Building the firmware --------------------- @@ -198,8 +74,7 @@ built-in scripts to bytecode. This can be done by (from the root of this repository): ```bash -$ cd mpy-cross -$ make mpy-cross +$ make -C mpy-cross ``` Then to build MicroPython for the ESP32 run: @@ -210,16 +85,14 @@ $ make submodules $ make ``` -This will produce binary firmware images in the `build/` subdirectory -(three of them: bootloader.bin, partitions.bin and application.bin). +This will produce a combined `firmware.bin` image in the `build-GENERIC/` +subdirectory (this firmware image is made up of: bootloader.bin, partitions.bin +and micropython.bin). To flash the firmware you must have your ESP32 module in the bootloader mode and connected to a serial port on your PC. Refer to the documentation -for your particular ESP32 module for how to do this. The serial port and -flash settings are set in the `Makefile`, and can be overridden in your -local `makefile`; see above for more details. - -You will also need to have user permissions to access the /dev/ttyUSB0 device. +for your particular ESP32 module for how to do this. +You will also need to have user permissions to access the `/dev/ttyUSB0` device. On Linux, you can enable this by adding your user to the `dialout` group, and rebooting or logging out and in again. (Note: on some distributions this may be the `uucp` group, run `ls -la /dev/ttyUSB0` to check.) @@ -242,8 +115,23 @@ To flash the MicroPython firmware to your ESP32 use: $ make deploy ``` -This will use the `esptool.py` script (provided by ESP-IDF) to flash the -binary images to the device. +The default ESP32 board build by the above commands is the `GENERIC` one, which +should work on most ESP32 modules. You can specify a different board by passing +`BOARD=` to the make commands, for example: + +```bash +$ make BOARD=GENERIC_SPIRAM +``` + +Note: the above "make" commands are thin wrappers for the underlying `idf.py` +build tool that is part of the ESP-IDF. You can instead use `idf.py` directly, +for example: + +```bash +$ idf.py build +$ idf.py -D MICROPY_BOARD=GENERIC_SPIRAM build +$ idf.py flash +``` Getting a Python prompt on the device ------------------------------------- @@ -262,6 +150,8 @@ or $ miniterm.py /dev/ttyUSB0 115200 ``` +You can also use `idf.py monitor`. + Configuring the WiFi and using the board ---------------------------------------- @@ -301,9 +191,27 @@ import machine antenna = machine.Pin(16, machine.Pin.OUT, value=0) ``` +Defining a custom ESP32 board +----------------------------- + +The default ESP-IDF configuration settings are provided by the `GENERIC` +board definition in the directory `boards/GENERIC`. For a custom configuration +you can define your own board directory. Start a new board configuration by +copying an existing one (like `GENERIC`) and modifying it to suit your board. + +MicroPython specific configuration values are defined in the board-specific +`mpconfigboard.h` file, which is included by `mpconfigport.h`. Additional +settings are put in `mpconfigboard.cmake`, including a list of `sdkconfig` +files that configure ESP-IDF settings. Some standard `sdkconfig` files are +provided in the `boards/` directory, like `boards/sdkconfig.ble`. You can +also define custom ones in your board directory. + +See existing board definitions for further examples of configuration. + +Configuration Troubleshooting --------------- -* Continuous reboots after programming: Ensure FLASH_MODE is correct for your - board (e.g. ESP-WROOM-32 should be DIO). Then perform a `make clean`, rebuild, - redeploy. +* Continuous reboots after programming: Ensure `CONFIG_ESPTOOLPY_FLASHMODE` is + correct for your board (e.g. ESP-WROOM-32 should be DIO). Then perform a + `make clean`, rebuild, redeploy. diff --git a/ports/esp32/boards/GENERIC/mpconfigboard.cmake b/ports/esp32/boards/GENERIC/mpconfigboard.cmake new file mode 100644 index 000000000..f69b05f47 --- /dev/null +++ b/ports/esp32/boards/GENERIC/mpconfigboard.cmake @@ -0,0 +1,6 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) diff --git a/ports/esp32/boards/GENERIC_D2WD/mpconfigboard.cmake b/ports/esp32/boards/GENERIC_D2WD/mpconfigboard.cmake new file mode 100644 index 000000000..686c78df9 --- /dev/null +++ b/ports/esp32/boards/GENERIC_D2WD/mpconfigboard.cmake @@ -0,0 +1,7 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/GENERIC_D2WD/sdkconfig.board +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) diff --git a/ports/esp32/boards/GENERIC_D2WD/sdkconfig.board b/ports/esp32/boards/GENERIC_D2WD/sdkconfig.board new file mode 100644 index 000000000..367283ded --- /dev/null +++ b/ports/esp32/boards/GENERIC_D2WD/sdkconfig.board @@ -0,0 +1,5 @@ +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_40M=y +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-2MiB.csv" diff --git a/ports/esp32/boards/GENERIC_OTA/mpconfigboard.cmake b/ports/esp32/boards/GENERIC_OTA/mpconfigboard.cmake new file mode 100644 index 000000000..ca552d470 --- /dev/null +++ b/ports/esp32/boards/GENERIC_OTA/mpconfigboard.cmake @@ -0,0 +1,7 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/GENERIC_OTA/sdkconfig.board +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) diff --git a/ports/esp32/boards/GENERIC_OTA/sdkconfig.board b/ports/esp32/boards/GENERIC_OTA/sdkconfig.board index b0ed171d8..d314860cc 100644 --- a/ports/esp32/boards/GENERIC_OTA/sdkconfig.board +++ b/ports/esp32/boards/GENERIC_OTA/sdkconfig.board @@ -1,4 +1,3 @@ CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y - -# ESP-IDF v3: -CONFIG_APP_ROLLBACK_ENABLE=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-ota.csv" diff --git a/ports/esp32/boards/GENERIC_SPIRAM/mpconfigboard.cmake b/ports/esp32/boards/GENERIC_SPIRAM/mpconfigboard.cmake new file mode 100644 index 000000000..2128edb59 --- /dev/null +++ b/ports/esp32/boards/GENERIC_SPIRAM/mpconfigboard.cmake @@ -0,0 +1,7 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.spiram +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) diff --git a/ports/esp32/boards/TINYPICO/mpconfigboard.cmake b/ports/esp32/boards/TINYPICO/mpconfigboard.cmake new file mode 100644 index 000000000..5c31f5c60 --- /dev/null +++ b/ports/esp32/boards/TINYPICO/mpconfigboard.cmake @@ -0,0 +1,9 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.240mhz + boards/sdkconfig.spiram + boards/TINYPICO/sdkconfig.board +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 67e2424a1..91e68c7bf 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -4,6 +4,12 @@ CONFIG_IDF_TARGET="esp32" CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 +# Compiler options: use -Os to reduce size, but keep full assertions +# (CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is for IDF 4.0.2) +CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y + # Application manager CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y @@ -41,13 +47,11 @@ CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y # ULP coprocessor support CONFIG_ESP32_ULP_COPROC_ENABLED=y -# v3.3-only (renamed in 4.0) -CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y -CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=n -CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=n -CONFIG_SUPPORT_STATIC_ALLOCATION=y -CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y -CONFIG_PPP_SUPPORT=y -CONFIG_PPP_PAP_SUPPORT=y -CONFIG_PPP_CHAP_SUPPORT=y -CONFIG_ULP_COPROC_ENABLED=y +# For cmake build +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + +# To reduce iRAM usage +CONFIG_ESP32_WIFI_IRAM_OPT=n +CONFIG_ESP32_WIFI_RX_IRAM_OPT=n diff --git a/ports/esp32/boards/sdkconfig.ble b/ports/esp32/boards/sdkconfig.ble index f714ce462..5565fd81a 100644 --- a/ports/esp32/boards/sdkconfig.ble +++ b/ports/esp32/boards/sdkconfig.ble @@ -15,13 +15,3 @@ CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4 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 -CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= -CONFIG_BTDM_CONTROLLER_MODE_BTDM= -CONFIG_BLUEDROID_ENABLED=n -CONFIG_NIMBLE_ENABLED=y -CONFIG_NIMBLE_MAX_CONNECTIONS=4 -CONFIG_NIMBLE_PINNED_TO_CORE_0=n -CONFIG_NIMBLE_PINNED_TO_CORE_1=y diff --git a/ports/esp32/boards/sdkconfig.spiram b/ports/esp32/boards/sdkconfig.spiram index db1a83af8..5b4ce118b 100644 --- a/ports/esp32/boards/sdkconfig.spiram +++ b/ports/esp32/boards/sdkconfig.spiram @@ -4,6 +4,3 @@ CONFIG_ESP32_SPIRAM_SUPPORT=y CONFIG_SPIRAM_CACHE_WORKAROUND=y CONFIG_SPIRAM_IGNORE_NOTFOUND=y CONFIG_SPIRAM_USE_MEMMAP=y - -# v3.3-only (renamed in 4.0) -CONFIG_SPIRAM_SUPPORT=y diff --git a/ports/esp32/esp32_nvs.c b/ports/esp32/esp32_nvs.c new file mode 100644 index 000000000..d13151d3c --- /dev/null +++ b/ports/esp32/esp32_nvs.c @@ -0,0 +1,151 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 by Thorsten von Eicken + * + * 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 "mphalport.h" +#include "modesp32.h" +#include "nvs_flash.h" +#include "nvs.h" + +// This file implements the NVS (Non-Volatile Storage) class in the esp32 module. +// It provides simple access to the NVS feature provided by ESP-IDF. + +// NVS python object that represents an NVS namespace. +typedef struct _esp32_nvs_obj_t { + mp_obj_base_t base; + nvs_handle_t namespace; +} esp32_nvs_obj_t; + +// *esp32_nvs_new allocates a python NVS object given a handle to an esp-idf namespace C obj. +STATIC esp32_nvs_obj_t *esp32_nvs_new(nvs_handle_t namespace) { + esp32_nvs_obj_t *self = m_new_obj(esp32_nvs_obj_t); + self->base.type = &esp32_nvs_type; + self->namespace = namespace; + return self; +} + +// esp32_nvs_print prints an NVS object, unfortunately it doesn't seem possible to extract the +// namespace string or anything else from the opaque handle provided by esp-idf. +STATIC void esp32_nvs_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + // esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, ""); +} + +// esp32_nvs_make_new constructs a handle to an NVS namespace. +STATIC mp_obj_t esp32_nvs_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, 1, 1, false); + + // Get requested nvs namespace + const char *ns_name = mp_obj_str_get_str(all_args[0]); + nvs_handle_t namespace; + check_esp_err(nvs_open(ns_name, NVS_READWRITE, &namespace)); + return MP_OBJ_FROM_PTR(esp32_nvs_new(namespace)); +} + +// esp32_nvs_set_i32 sets a 32-bit integer value +STATIC mp_obj_t esp32_nvs_set_i32(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + int32_t value = mp_obj_get_int(value_in); + check_esp_err(nvs_set_i32(self->namespace, key, value)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_set_i32_obj, esp32_nvs_set_i32); + +// esp32_nvs_get_i32 reads a 32-bit integer value +STATIC mp_obj_t esp32_nvs_get_i32(mp_obj_t self_in, mp_obj_t key_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + int32_t value; + check_esp_err(nvs_get_i32(self->namespace, key, &value)); + return mp_obj_new_int(value); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_nvs_get_i32_obj, esp32_nvs_get_i32); + +// esp32_nvs_set_blob writes a buffer object into a binary blob value. +STATIC mp_obj_t esp32_nvs_set_blob(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + mp_buffer_info_t value; + mp_get_buffer_raise(value_in, &value, MP_BUFFER_READ); + check_esp_err(nvs_set_blob(self->namespace, key, value.buf, value.len)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_set_blob_obj, esp32_nvs_set_blob); + +// esp32_nvs_get_blob reads a binary blob value into a bytearray. Returns actual length. +STATIC mp_obj_t esp32_nvs_get_blob(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + // get buffer to be filled + mp_buffer_info_t value; + mp_get_buffer_raise(value_in, &value, MP_BUFFER_WRITE); + size_t length = value.len; + // fill the buffer with the value, will raise an esp-idf error if the length of + // the provided buffer (bytearray) is too small + check_esp_err(nvs_get_blob(self->namespace, key, value.buf, &length)); + return MP_OBJ_NEW_SMALL_INT(length); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_get_blob_obj, esp32_nvs_get_blob); + +// esp32_nvs_erase_key erases one key. +STATIC mp_obj_t esp32_nvs_erase_key(mp_obj_t self_in, mp_obj_t key_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + check_esp_err(nvs_erase_key(self->namespace, key)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_nvs_erase_key_obj, esp32_nvs_erase_key); + +// esp32_nvs_commit commits any changes to flash. +STATIC mp_obj_t esp32_nvs_commit(mp_obj_t self_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(nvs_commit(self->namespace)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_nvs_commit_obj, esp32_nvs_commit); + +STATIC const mp_rom_map_elem_t esp32_nvs_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_get_i32), MP_ROM_PTR(&esp32_nvs_get_i32_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_i32), MP_ROM_PTR(&esp32_nvs_set_i32_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_blob), MP_ROM_PTR(&esp32_nvs_get_blob_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_blob), MP_ROM_PTR(&esp32_nvs_set_blob_obj) }, + { MP_ROM_QSTR(MP_QSTR_erase_key), MP_ROM_PTR(&esp32_nvs_erase_key_obj) }, + { MP_ROM_QSTR(MP_QSTR_commit), MP_ROM_PTR(&esp32_nvs_commit_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_nvs_locals_dict, esp32_nvs_locals_dict_table); + +const mp_obj_type_t esp32_nvs_type = { + { &mp_type_type }, + .name = MP_QSTR_NVS, + .print = esp32_nvs_print, + .make_new = esp32_nvs_make_new, + .locals_dict = (mp_obj_dict_t *)&esp32_nvs_locals_dict, +}; diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 7971ca5d1..b547af553 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -209,7 +209,7 @@ STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *pos_args, mp_obj_t pulses = args[1].u_obj; mp_uint_t start = args[2].u_int; - if (start < 0 || start > 1) { + if (start > 1) { mp_raise_ValueError(MP_ERROR_TEXT("start must be 0 or 1")); } diff --git a/ports/esp32/esp32_ulp.c b/ports/esp32/esp32_ulp.c index 4e9d76814..50244cdf2 100644 --- a/ports/esp32/esp32_ulp.c +++ b/ports/esp32/esp32_ulp.c @@ -85,11 +85,7 @@ STATIC const mp_rom_map_elem_t esp32_ulp_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_wakeup_period), MP_ROM_PTR(&esp32_ulp_set_wakeup_period_obj) }, { MP_ROM_QSTR(MP_QSTR_load_binary), MP_ROM_PTR(&esp32_ulp_load_binary_obj) }, { MP_ROM_QSTR(MP_QSTR_run), MP_ROM_PTR(&esp32_ulp_run_obj) }, - #if !MICROPY_ESP_IDF_4 - { MP_ROM_QSTR(MP_QSTR_RESERVE_MEM), MP_ROM_INT(CONFIG_ULP_COPROC_RESERVE_MEM) }, - #else { MP_ROM_QSTR(MP_QSTR_RESERVE_MEM), MP_ROM_INT(CONFIG_ESP32_ULP_COPROC_RESERVE_MEM) }, - #endif }; STATIC MP_DEFINE_CONST_DICT(esp32_ulp_locals_dict, esp32_ulp_locals_dict_table); diff --git a/ports/esp32/fatfs_port.c b/ports/esp32/fatfs_port.c index cfc52853d..7fce654c0 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_hw_spi.c b/ports/esp32/machine_hw_spi.c index 3962e26b7..3790b4e0c 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 8362ed5e7..3993c7b52 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) { @@ -115,10 +115,12 @@ 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[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, @@ -169,11 +171,11 @@ 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, .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/esp32/machine_pin.c b/ports/esp32/machine_pin.c index a55cc1f4a..dcdd53be9 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); diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index 6902ce85f..1b6a71b5b 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/machine_uart.c b/ports/esp32/machine_uart.c index c334e694b..7fce83f2c 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -36,6 +36,20 @@ #include "py/mperrno.h" #include "modmachine.h" +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) +#define UART_INV_TX UART_INVERSE_TXD +#define UART_INV_RX UART_INVERSE_RXD +#define UART_INV_RTS UART_INVERSE_RTS +#define UART_INV_CTS UART_INVERSE_CTS +#else +#define UART_INV_TX UART_SIGNAL_TXD_INV +#define UART_INV_RX UART_SIGNAL_RXD_INV +#define UART_INV_RTS UART_SIGNAL_RTS_INV +#define UART_INV_CTS UART_SIGNAL_CTS_INV +#endif + +#define UART_INV_MASK (UART_INV_TX | UART_INV_RX | UART_INV_RTS | UART_INV_CTS) + typedef struct _machine_uart_obj_t { mp_obj_base_t base; uart_port_t uart_num; @@ -68,28 +82,28 @@ STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_pri if (self->invert) { mp_printf(print, ", invert="); uint32_t invert_mask = self->invert; - if (invert_mask & UART_INVERSE_TXD) { + if (invert_mask & UART_INV_TX) { mp_printf(print, "INV_TX"); - invert_mask &= ~UART_INVERSE_TXD; + invert_mask &= ~UART_INV_TX; if (invert_mask) { mp_printf(print, "|"); } } - if (invert_mask & UART_INVERSE_RXD) { + if (invert_mask & UART_INV_RX) { mp_printf(print, "INV_RX"); - invert_mask &= ~UART_INVERSE_RXD; + invert_mask &= ~UART_INV_RX; if (invert_mask) { mp_printf(print, "|"); } } - if (invert_mask & UART_INVERSE_RTS) { + if (invert_mask & UART_INV_RTS) { mp_printf(print, "INV_RTS"); - invert_mask &= ~UART_INVERSE_RTS; + invert_mask &= ~UART_INV_RTS; if (invert_mask) { mp_printf(print, "|"); } } - if (invert_mask & UART_INVERSE_CTS) { + if (invert_mask & UART_INV_CTS) { mp_printf(print, "INV_CTS"); } } @@ -238,7 +252,7 @@ STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, co } // set line inversion - if (args[ARG_invert].u_int & ~UART_LINE_INV_MASK) { + if (args[ARG_invert].u_int & ~UART_INV_MASK) { mp_raise_ValueError(MP_ERROR_TEXT("invalid inversion mask")); } self->invert = args[ARG_invert].u_int; @@ -380,10 +394,10 @@ STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, { MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) }, - { MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_INVERSE_TXD) }, - { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INVERSE_RXD) }, - { MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_INVERSE_RTS) }, - { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INVERSE_CTS) }, + { MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_INV_TX) }, + { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INV_RX) }, + { MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_INV_RTS) }, + { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INV_CTS) }, }; STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 6f9ab82d0..7413798d0 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -37,11 +37,7 @@ #include "esp_task.h" #include "soc/cpu.h" #include "esp_log.h" -#if MICROPY_ESP_IDF_4 #include "esp32/spiram.h" -#else -#include "esp_spiram.h" -#endif #include "py/stackctrl.h" #include "py/nlr.h" @@ -77,6 +73,7 @@ void mp_task(void *pvParameter) { mp_thread_init(pxTaskGetStackStart(NULL), MP_TASK_STACK_SIZE / sizeof(uintptr_t)); #endif uart_init(); + machine_init(); // TODO: CONFIG_SPIRAM_SUPPORT is for 3.3 compatibility, remove after move to 4.0. #if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_SPIRAM_SUPPORT @@ -122,7 +119,10 @@ void mp_task(void *pvParameter) { pyexec_frozen_module("_boot.py"); pyexec_file_if_exists("boot.py"); if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { - pyexec_file_if_exists("main.py"); + int ret = pyexec_file_if_exists("main.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } } for (;;) { @@ -139,6 +139,8 @@ void mp_task(void *pvParameter) { } } +soft_reset_exit: + #if MICROPY_BLUETOOTH_NIMBLE mp_bluetooth_deinit(); #endif @@ -155,6 +157,7 @@ void mp_task(void *pvParameter) { // deinitialise peripherals machine_pins_deinit(); + machine_deinit(); usocket_events_deinit(); mp_deinit(); diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt new file mode 100644 index 000000000..bd25d76ee --- /dev/null +++ b/ports/esp32/main/CMakeLists.txt @@ -0,0 +1,196 @@ +# Set location of base MicroPython directory. +get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../.. ABSOLUTE) + +# Include core source components. +include(${MICROPY_DIR}/py/py.cmake) +include(${MICROPY_DIR}/extmod/extmod.cmake) + +set(MICROPY_QSTRDEFS_PORT + ${PROJECT_DIR}/qstrdefsport.h +) + +set(MICROPY_SOURCE_EXTMOD_EXTRA + ${MICROPY_DIR}/extmod/modonewire.c +) + +set(MICROPY_SOURCE_LIB + ${MICROPY_DIR}/lib/littlefs/lfs1.c + ${MICROPY_DIR}/lib/littlefs/lfs1_util.c + ${MICROPY_DIR}/lib/littlefs/lfs2.c + ${MICROPY_DIR}/lib/littlefs/lfs2_util.c + ${MICROPY_DIR}/lib/mbedtls_errors/mp_mbedtls_errors.c + ${MICROPY_DIR}/lib/mp-readline/readline.c + ${MICROPY_DIR}/lib/netutils/netutils.c + ${MICROPY_DIR}/lib/oofatfs/ff.c + ${MICROPY_DIR}/lib/oofatfs/ffunicode.c + ${MICROPY_DIR}/lib/timeutils/timeutils.c + ${MICROPY_DIR}/lib/utils/interrupt_char.c + ${MICROPY_DIR}/lib/utils/stdout_helpers.c + ${MICROPY_DIR}/lib/utils/sys_stdio_mphal.c + ${MICROPY_DIR}/lib/utils/pyexec.c +) + +set(MICROPY_SOURCE_DRIVERS + ${MICROPY_DIR}/drivers/bus/softspi.c + ${MICROPY_DIR}/drivers/dht/dht.c +) + +set(MICROPY_SOURCE_PORT + ${PROJECT_DIR}/main.c + ${PROJECT_DIR}/uart.c + ${PROJECT_DIR}/gccollect.c + ${PROJECT_DIR}/mphalport.c + ${PROJECT_DIR}/fatfs_port.c + ${PROJECT_DIR}/help.c + ${PROJECT_DIR}/modutime.c + ${PROJECT_DIR}/moduos.c + ${PROJECT_DIR}/machine_timer.c + ${PROJECT_DIR}/machine_pin.c + ${PROJECT_DIR}/machine_touchpad.c + ${PROJECT_DIR}/machine_adc.c + ${PROJECT_DIR}/machine_dac.c + ${PROJECT_DIR}/machine_i2c.c + ${PROJECT_DIR}/machine_pwm.c + ${PROJECT_DIR}/machine_uart.c + ${PROJECT_DIR}/modmachine.c + ${PROJECT_DIR}/modnetwork.c + ${PROJECT_DIR}/network_lan.c + ${PROJECT_DIR}/network_ppp.c + ${PROJECT_DIR}/mpnimbleport.c + ${PROJECT_DIR}/modsocket.c + ${PROJECT_DIR}/modesp.c + ${PROJECT_DIR}/esp32_nvs.c + ${PROJECT_DIR}/esp32_partition.c + ${PROJECT_DIR}/esp32_rmt.c + ${PROJECT_DIR}/esp32_ulp.c + ${PROJECT_DIR}/modesp32.c + ${PROJECT_DIR}/espneopixel.c + ${PROJECT_DIR}/machine_hw_spi.c + ${PROJECT_DIR}/machine_wdt.c + ${PROJECT_DIR}/mpthreadport.c + ${PROJECT_DIR}/machine_rtc.c + ${PROJECT_DIR}/machine_sdcard.c +) + +set(MICROPY_SOURCE_QSTR + ${MICROPY_SOURCE_PY} + ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_EXTMOD_EXTRA} + ${MICROPY_SOURCE_LIB} + ${MICROPY_SOURCE_PORT} +) + +set(IDF_COMPONENTS + app_update + bootloader_support + bt + driver + esp32 + esp_common + esp_eth + esp_event + esp_ringbuf + esp_rom + esp_wifi + freertos + heap + log + lwip + mbedtls + mdns + newlib + nvs_flash + sdmmc + soc + spi_flash + tcpip_adapter + ulp + vfs + xtensa +) + +if(IDF_VERSION_MINOR GREATER_EQUAL 1) + list(APPEND IDF_COMPONENTS esp_netif) +endif() + +if(IDF_VERSION_MINOR GREATER_EQUAL 2) + list(APPEND IDF_COMPONENTS esp_system) + list(APPEND IDF_COMPONENTS esp_timer) +endif() + +if(IDF_VERSION_MINOR GREATER_EQUAL 3) + list(APPEND IDF_COMPONENTS esp_hw_support) + list(APPEND IDF_COMPONENTS esp_pm) + list(APPEND IDF_COMPONENTS hal) +endif() + +# Register the main IDF component. +idf_component_register( + SRCS + ${MICROPY_SOURCE_PY} + ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_EXTMOD_EXTRA} + ${MICROPY_SOURCE_LIB} + ${MICROPY_SOURCE_DRIVERS} + ${MICROPY_SOURCE_PORT} + INCLUDE_DIRS + ${MICROPY_DIR} + ${MICROPY_PORT_DIR} + ${MICROPY_BOARD_DIR} + ${CMAKE_BINARY_DIR} + REQUIRES + ${IDF_COMPONENTS} +) + +# Set the MicroPython target as the current (main) IDF component target. +set(MICROPY_TARGET ${COMPONENT_TARGET}) + +# Define mpy-cross flags, for use with frozen code. +set(MICROPY_CROSS_FLAGS -march=xtensawin) + +# Set compile options for this port. +target_compile_definitions(${MICROPY_TARGET} PUBLIC + MICROPY_ESP_IDF_4=1 + MICROPY_VFS_FAT=1 + MICROPY_VFS_LFS2=1 + FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\" + LFS1_NO_MALLOC LFS1_NO_DEBUG LFS1_NO_WARN LFS1_NO_ERROR LFS1_NO_ASSERT + LFS2_NO_MALLOC LFS2_NO_DEBUG LFS2_NO_WARN LFS2_NO_ERROR LFS2_NO_ASSERT +) + +# Disable some warnings to keep the build output clean. +target_compile_options(${MICROPY_TARGET} PUBLIC + -Wno-clobbered + -Wno-deprecated-declarations + -Wno-missing-field-initializers +) + +# Collect all of the include directories and compile definitions for the IDF components. +foreach(comp ${IDF_COMPONENTS}) + get_target_property(type __idf_${comp} TYPE) + set(_inc OFF) + set(_def OFF) + if(${type} STREQUAL STATIC_LIBRARY) + get_target_property(_inc __idf_${comp} INCLUDE_DIRECTORIES) + get_target_property(_def __idf_${comp} COMPILE_DEFINITIONS) + elseif(${type} STREQUAL INTERFACE_LIBRARY) + get_target_property(_inc __idf_${comp} INTERFACE_INCLUDE_DIRECTORIES) + get_target_property(_def __idf_${comp} INTERFACE_COMPILE_DEFINITIONS) + endif() + if(_inc) + list(APPEND MICROPY_CPP_INC_EXTRA ${_inc}) + endif() + if(_def) + list(APPEND MICROPY_CPP_DEF_EXTRA ${_def}) + endif() +endforeach() + +if(IDF_VERSION_MINOR GREATER_EQUAL 2) + # These paths cannot currently be found by the IDF_COMPONENTS search loop above, + # so add them explicitly. + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/${IDF_TARGET}/include) + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/include) +endif() + +# Include the main MicroPython cmake rules. +include(${MICROPY_DIR}/py/mkrules.cmake) diff --git a/ports/esp32/modesp.c b/ports/esp32/modesp.c index 369649c11..59a261e8c 100644 --- a/ports/esp32/modesp.c +++ b/ports/esp32/modesp.c @@ -29,9 +29,6 @@ #include -#if !MICROPY_ESP_IDF_4 -#include "rom/gpio.h" -#endif #include "esp_log.h" #include "esp_spi_flash.h" diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 8e248370f..53ca7fdc6 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -35,17 +35,23 @@ #include "driver/adc.h" #include "esp_heap_caps.h" #include "multi_heap.h" -#include "../heap_private.h" #include "py/nlr.h" #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" +// These private includes are needed for idf_heap_info. +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +#define MULTI_HEAP_FREERTOS +#include "../multi_heap_platform.h" +#endif +#include "../heap_private.h" + STATIC mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { if (machine_rtc_config.ext0_pin != -1) { @@ -180,6 +186,7 @@ STATIC const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_hall_sensor), MP_ROM_PTR(&esp32_hall_sensor_obj) }, { MP_ROM_QSTR(MP_QSTR_idf_heap_info), MP_ROM_PTR(&esp32_idf_heap_info_obj) }, + { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, { MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) }, diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index f4c0491f7..18bd62ee4 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -26,6 +26,7 @@ #define RTC_LAST_EXT_PIN 39 #define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS) +extern const mp_obj_type_t esp32_nvs_type; extern const mp_obj_type_t esp32_partition_type; extern const mp_obj_type_t esp32_rmt_type; extern const mp_obj_type_t esp32_ulp_type; diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 82a02522f..3925bcb64 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -32,15 +32,9 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#if MICROPY_ESP_IDF_4 #include "esp32/rom/rtc.h" #include "esp32/clk.h" #include "esp_sleep.h" -#else -#include "rom/ets_sys.h" -#include "rom/rtc.h" -#include "esp_clk.h" -#endif #include "esp_pm.h" #include "driver/touch_pad.h" @@ -65,6 +59,8 @@ typedef enum { MP_SOFT_RESET } reset_reason_t; +STATIC bool is_soft_reset = 0; + STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { // get @@ -146,6 +142,9 @@ STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *pos_args, mp_ma STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_deepsleep_obj, 0, machine_deepsleep); STATIC mp_obj_t machine_reset_cause(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + if (is_soft_reset) { + return MP_OBJ_NEW_SMALL_INT(MP_SOFT_RESET); + } switch (esp_reset_reason()) { case ESP_RST_POWERON: case ESP_RST_BROWNOUT: @@ -177,6 +176,15 @@ STATIC mp_obj_t machine_reset_cause(size_t n_args, const mp_obj_t *pos_args, mp_ } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_reset_cause_obj, 0, machine_reset_cause); +void machine_init(void) { + is_soft_reset = 0; +} + +void machine_deinit(void) { + // we are doing a soft-reset so change the reset_cause + is_soft_reset = 1; +} + STATIC mp_obj_t machine_wake_reason(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { return MP_OBJ_NEW_SMALL_INT(esp_sleep_get_wakeup_cause()); } @@ -255,10 +263,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(&machine_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 fbb43250f..3e99e1120 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -16,11 +16,14 @@ 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; extern const mp_obj_type_t machine_sdcard_type; +void machine_init(void); +void machine_deinit(void); void machine_pins_init(void); void machine_pins_deinit(void); void machine_timer_deinit_all(void); diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 029a8d143..f772fa2cf 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -40,12 +40,11 @@ #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" #include "lwip/dns.h" -#include "tcpip_adapter.h" #include "mdns.h" #if !MICROPY_ESP_IDF_4 @@ -55,6 +54,12 @@ #include "modnetwork.h" +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) +#define DNS_MAIN TCPIP_ADAPTER_DNS_MAIN +#else +#define DNS_MAIN ESP_NETIF_DNS_MAIN +#endif + #define MODNETWORK_INCLUDE_CONSTANTS (1) NORETURN void _esp_exceptions(esp_err_t e) { @@ -331,7 +336,7 @@ STATIC mp_obj_t esp_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k 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); - wifi_config_t wifi_sta_config = {{{0}}}; + wifi_config_t wifi_sta_config = {0}; // configure any parameters that are given if (n_args > 1) { @@ -491,7 +496,7 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { tcpip_adapter_ip_info_t info; tcpip_adapter_dns_info_t dns_info; tcpip_adapter_get_ip_info(self->if_id, &info); - tcpip_adapter_get_dns_info(self->if_id, TCPIP_ADAPTER_DNS_MAIN, &dns_info); + tcpip_adapter_get_dns_info(self->if_id, DNS_MAIN, &dns_info); if (n_args == 1) { // get mp_obj_t tuple[4] = { @@ -526,14 +531,14 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { _esp_exceptions(e); } ESP_EXCEPTIONS(tcpip_adapter_set_ip_info(self->if_id, &info)); - ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(self->if_id, TCPIP_ADAPTER_DNS_MAIN, &dns_info)); + ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(self->if_id, DNS_MAIN, &dns_info)); } else if (self->if_id == WIFI_IF_AP) { esp_err_t e = tcpip_adapter_dhcps_stop(WIFI_IF_AP); if (e != ESP_OK && e != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { _esp_exceptions(e); } ESP_EXCEPTIONS(tcpip_adapter_set_ip_info(WIFI_IF_AP, &info)); - ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(WIFI_IF_AP, TCPIP_ADAPTER_DNS_MAIN, &dns_info)); + ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(WIFI_IF_AP, DNS_MAIN, &dns_info)); ESP_EXCEPTIONS(tcpip_adapter_dhcps_start(WIFI_IF_AP)); } } else { @@ -772,6 +777,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) }, diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index 85433e575..5135e3163 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -46,7 +46,6 @@ #include "py/stream.h" #include "py/mperrno.h" #include "lib/netutils/netutils.h" -#include "tcpip_adapter.h" #include "mdns.h" #include "modnetwork.h" @@ -56,18 +55,6 @@ #include "lwip/igmp.h" #include "esp_log.h" -#if !MICROPY_ESP_IDF_4 -#define lwip_bind lwip_bind_r -#define lwip_listen lwip_listen_r -#define lwip_accept lwip_accept_r -#define lwip_setsockopt lwip_setsockopt_r -#define lwip_fnctl lwip_fnctl_r -#define lwip_recvfrom lwip_recvfrom_r -#define lwip_write lwip_write_r -#define lwip_sendto lwip_sendto_r -#define lwip_close lwip_close_r -#endif - #define SOCKET_POLL_US (100000) #define MDNS_QUERY_TIMEOUT_MS (5000) #define MDNS_LOCAL_SUFFIX ".local" @@ -153,16 +140,6 @@ void usocket_events_handler(void) { #endif // MICROPY_PY_USOCKET_EVENTS -NORETURN static void exception_from_errno(int _errno) { - // Here we need to convert from lwip errno values to MicroPython's standard ones - if (_errno == EADDRINUSE) { - _errno = MP_EADDRINUSE; - } else if (_errno == EINPROGRESS) { - _errno = MP_EINPROGRESS; - } - mp_raise_OSError(_errno); -} - static inline void check_for_exceptions(void) { mp_handle_pending(true); } @@ -181,7 +158,12 @@ static int _socket_getaddrinfo3(const char *nodename, const char *servname, memcpy(nodename_no_local, nodename, nodename_len - local_len); nodename_no_local[nodename_len - local_len] = '\0'; + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) struct ip4_addr addr = {0}; + #else + esp_ip4_addr_t addr = {0}; + #endif + esp_err_t err = mdns_query_a(nodename_no_local, MDNS_QUERY_TIMEOUT_MS, &addr); if (err != ESP_OK) { if (err == ESP_ERR_NOT_FOUND) { @@ -285,7 +267,7 @@ STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, siz sock->fd = lwip_socket(sock->domain, sock->type, sock->proto); if (sock->fd < 0) { - exception_from_errno(errno); + mp_raise_OSError(errno); } _socket_settimeout(sock, UINT64_MAX); @@ -299,7 +281,7 @@ STATIC mp_obj_t socket_bind(const mp_obj_t arg0, const mp_obj_t arg1) { int r = lwip_bind(self->fd, res->ai_addr, res->ai_addrlen); lwip_freeaddrinfo(res); if (r < 0) { - exception_from_errno(errno); + mp_raise_OSError(errno); } return mp_const_none; } @@ -310,7 +292,7 @@ STATIC mp_obj_t socket_listen(const mp_obj_t arg0, const mp_obj_t arg1) { int backlog = mp_obj_get_int(arg1); int r = lwip_listen(self->fd, backlog); if (r < 0) { - exception_from_errno(errno); + mp_raise_OSError(errno); } return mp_const_none; } @@ -331,7 +313,7 @@ STATIC mp_obj_t socket_accept(const mp_obj_t arg0) { break; } if (errno != EAGAIN) { - exception_from_errno(errno); + mp_raise_OSError(errno); } check_for_exceptions(); } @@ -373,7 +355,7 @@ STATIC mp_obj_t socket_connect(const mp_obj_t arg0, const mp_obj_t arg1) { MP_THREAD_GIL_ENTER(); lwip_freeaddrinfo(res); if (r != 0) { - exception_from_errno(errno); + mp_raise_OSError(errno); } return mp_const_none; @@ -392,7 +374,7 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { int val = mp_obj_get_int(args[3]); int ret = lwip_setsockopt(self->fd, SOL_SOCKET, opt, &val, sizeof(int)); if (ret != 0) { - exception_from_errno(errno); + mp_raise_OSError(errno); } break; } @@ -543,7 +525,7 @@ mp_obj_t _socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in, int errcode; mp_uint_t ret = _socket_read_data(self_in, vstr.buf, len, from, from_len, &errcode); if (ret == MP_STREAM_ERROR) { - exception_from_errno(errcode); + mp_raise_OSError(errcode); } vstr.len = ret; @@ -576,8 +558,9 @@ int _socket_send(socket_obj_t *sock, const char *data, size_t datalen) { MP_THREAD_GIL_EXIT(); int r = lwip_write(sock->fd, data + sentlen, datalen - sentlen); MP_THREAD_GIL_ENTER(); - if (r < 0 && errno != EWOULDBLOCK) { - exception_from_errno(errno); + // lwip returns EINPROGRESS when trying to send right after a non-blocking connect + if (r < 0 && errno != EWOULDBLOCK && errno != EINPROGRESS) { + mp_raise_OSError(errno); } if (r > 0) { sentlen += r; @@ -585,7 +568,7 @@ int _socket_send(socket_obj_t *sock, const char *data, size_t datalen) { check_for_exceptions(); } if (sentlen == 0) { - mp_raise_OSError(MP_ETIMEDOUT); + mp_raise_OSError(sock->retries == 0 ? MP_EWOULDBLOCK : MP_ETIMEDOUT); } return sentlen; } @@ -635,7 +618,7 @@ STATIC mp_obj_t socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t addr_ return mp_obj_new_int_from_uint(ret); } if (ret == -1 && errno != EWOULDBLOCK) { - exception_from_errno(errno); + mp_raise_OSError(errno); } check_for_exceptions(); } @@ -668,7 +651,8 @@ STATIC mp_uint_t socket_stream_write(mp_obj_t self_in, const void *buf, mp_uint_ if (r > 0) { return r; } - if (r < 0 && errno != EWOULDBLOCK) { + // lwip returns MP_EINPROGRESS when trying to write right after a non-blocking connect + if (r < 0 && errno != EWOULDBLOCK && errno != EINPROGRESS) { *errcode = errno; return MP_STREAM_ERROR; } diff --git a/ports/esp32/modules/inisetup.py b/ports/esp32/modules/inisetup.py index 9995d6bc0..426a47a6b 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/esp32/modutime.c b/ports/esp32/modutime.c index ac3edb4e9..cf7178e0b 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/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 663fed3f6..392f8c749 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -6,10 +6,7 @@ #include #include - -#if !MICROPY_ESP_IDF_4 -#include "rom/ets_sys.h" -#endif +#include "esp_system.h" // object representation and NLR handling #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) @@ -54,7 +51,7 @@ #define MICROPY_MODULE_FROZEN_MPY (1) #define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool #define MICROPY_CAN_OVERRIDE_BUILTINS (1) -#define MICROPY_USE_INTERNAL_ERRNO (1) +#define MICROPY_USE_INTERNAL_ERRNO (0) // errno.h from xtensa-esp32-elf/sys-include/sys #define MICROPY_USE_INTERNAL_PRINTF (0) // ESP32 SDK requires its own printf #define MICROPY_ENABLE_SCHEDULER (1) #define MICROPY_SCHEDULER_DEPTH (8) @@ -63,6 +60,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) @@ -124,6 +122,12 @@ #define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32) // extended modules +#ifndef MICROPY_PY_BLUETOOTH +#define MICROPY_PY_BLUETOOTH (1) +#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) +#define MICROPY_BLUETOOTH_NIMBLE (1) +#define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (1) +#endif #define MICROPY_PY_UASYNCIO (1) #define MICROPY_PY_UCTYPES (1) #define MICROPY_PY_UZLIB (1) @@ -140,16 +144,15 @@ #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 #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) -#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 @@ -220,9 +223,7 @@ struct mp_bluetooth_nimble_root_pointers_t; // type definitions for the specific machine -#define BYTES_PER_WORD (4) #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p))) -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) void *esp_native_code_commit(void *, size_t, void *); #define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) #define MP_SSIZE_MAX (0x7fffffff) diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c index ad571bf96..65fa88e4d 100644 --- a/ports/esp32/mphalport.c +++ b/ports/esp32/mphalport.c @@ -32,12 +32,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" - -#if MICROPY_ESP_IDF_4 #include "esp32/rom/uart.h" -#else -#include "rom/uart.h" -#endif #include "py/obj.h" #include "py/objstr.h" @@ -52,7 +47,7 @@ TaskHandle_t mp_main_task_handle; STATIC uint8_t stdin_ringbuf_array[256]; -ringbuf_t stdin_ringbuf = {stdin_ringbuf_array, sizeof(stdin_ringbuf_array)}; +ringbuf_t stdin_ringbuf = {stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, 0}; // Check the ESP-IDF error code and raise an OSError if it's not ESP_OK. void check_esp_err(esp_err_t code) { diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index d294c9272..140a76464 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -34,9 +34,6 @@ #include "mpthreadport.h" #include "esp_task.h" -#if !MICROPY_ESP_IDF_4 -#include "freertos/semphr.h" -#endif #if MICROPY_PY_THREAD diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index df57b8172..db2292ca0 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" diff --git a/ports/esp32/uart.c b/ports/esp32/uart.c index 381be7f4f..c837c8dcf 100644 --- a/ports/esp32/uart.c +++ b/ports/esp32/uart.c @@ -29,6 +29,7 @@ #include #include "driver/uart.h" +#include "soc/uart_periph.h" #include "py/runtime.h" #include "py/mphal.h" diff --git a/ports/esp8266/gccollect.c b/ports/esp8266/gccollect.c index 3618a5664..bca0e030c 100644 --- a/ports/esp8266/gccollect.c +++ b/ports/esp8266/gccollect.c @@ -26,6 +26,7 @@ #include +#include "py/mpconfig.h" #include "py/gc.h" #include "gccollect.h" diff --git a/ports/esp8266/gccollect.h b/ports/esp8266/gccollect.h index 4323e9507..b86d3d6e1 100644 --- a/ports/esp8266/gccollect.h +++ b/ports/esp8266/gccollect.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_ESP8266_GCCOLLECT_H #define MICROPY_INCLUDED_ESP8266_GCCOLLECT_H +#include + extern uint32_t _text_start; extern uint32_t _text_end; extern uint32_t _irom0_text_start; diff --git a/ports/esp8266/machine_hspi.c b/ports/esp8266/machine_hspi.c index 7319194d7..ff3ba1725 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 @@ -178,7 +180,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/modmachine.c b/ports/esp8266/modmachine.c index cb28d5b14..86c1d728d 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" @@ -421,10 +422,12 @@ 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) }, + { 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/esp8266/modules/inisetup.py b/ports/esp8266/modules/inisetup.py index 79576e9d6..9500b8048 100644 --- a/ports/esp8266/modules/inisetup.py +++ b/ports/esp8266/modules/inisetup.py @@ -30,13 +30,12 @@ def fs_corrupted(): while 1: print( """\ -The FAT 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 -programming). +The filesystem starting at sector %d with size %d sectors looks corrupt. +You may want to make a flash snapshot and try to recover it. Otherwise, +format it with uos.VfsLfs2.mkfs(bdev), or completely erase the flash and +reprogram MicroPython. """ - % (bdev.START_SEC, bdev.blocks) + % (bdev.start_sec, bdev.blocks) ) time.sleep(3) diff --git a/ports/esp8266/modules/neopixel.py b/ports/esp8266/modules/neopixel.py index be56ed1b0..501a2689e 100644 --- a/ports/esp8266/modules/neopixel.py +++ b/ports/esp8266/modules/neopixel.py @@ -7,12 +7,13 @@ 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 @@ def fill(self, color): self[i] = color def write(self): - neopixel_write(self.pin, self.buf, True) + neopixel_write(self.pin, self.buf, self.timing) diff --git a/ports/esp8266/modutime.c b/ports/esp8266/modutime.c index 86534722c..bcfbf7baf 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/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 5344a98d6..fe3b85ea6 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -21,8 +21,8 @@ #define MICROPY_STACK_CHECK (1) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_KBD_EXCEPTION (1) -#define MICROPY_REPL_EVENT_DRIVEN (0) -#define MICROPY_REPL_AUTO_INDENT (1) +#define MICROPY_REPL_EVENT_DRIVEN (1) +#define MICROPY_REPL_AUTO_INDENT (0) #define MICROPY_HELPER_REPL (1) #define MICROPY_HELPER_LEXER_UNIX (0) #define MICROPY_ENABLE_SOURCE_LINE (1) @@ -182,7 +182,7 @@ extern const struct _mp_obj_module_t mp_module_onewire; #define MP_STATE_PORT MP_STATE_VM #define MICROPY_PORT_ROOT_POINTERS \ - const char *readline_hist[8]; \ + const char *readline_hist[4]; \ mp_obj_t pin_irq_handler[16]; \ byte *uart0_rxbuf; \ @@ -199,3 +199,5 @@ extern const struct _mp_obj_module_t mp_module_onewire; #define MICROPY_WRAP_MP_SCHED_SCHEDULE(f) MP_FASTCODE(f) #define _assert(expr) ((expr) ? (void)0 : __assert_func(__FILE__, __LINE__, __func__, #expr)) + + diff --git a/ports/javascript/mpconfigport.h b/ports/javascript/mpconfigport.h index 0101e10c8..d1bfbbd60 100644 --- a/ports/javascript/mpconfigport.h +++ b/ports/javascript/mpconfigport.h @@ -180,8 +180,6 @@ typedef int mp_int_t; // must be pointer size typedef unsigned mp_uint_t; // must be pointer size typedef long mp_off_t; -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - // extra built in names to add to the global namespace #define MICROPY_PORT_BUILTINS \ { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, diff --git a/ports/mimxrt/board_init.c b/ports/mimxrt/board_init.c index 6f5235d3b..cd7dc9a7d 100644 --- a/ports/mimxrt/board_init.c +++ b/ports/mimxrt/board_init.c @@ -90,11 +90,13 @@ void SysTick_Handler(void) { } void USB_OTG1_IRQHandler(void) { - tud_isr(0); + tud_int_handler(0); tud_task(); + __SEV(); } void USB_OTG2_IRQHandler(void) { - tud_isr(1); + tud_int_handler(1); tud_task(); + __SEV(); } diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 344867fb8..68c7a8942 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -94,11 +94,10 @@ extern const struct _mp_obj_module_t mp_module_utime; do { \ extern void mp_handle_pending(bool); \ mp_handle_pending(true); \ - __WFI(); \ + __WFE(); \ } while (0); #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) #define MP_SSIZE_MAX (0x7fffffff) typedef int mp_int_t; // must be pointer size diff --git a/ports/mimxrt/mphalport.c b/ports/mimxrt/mphalport.c index d7642d6b6..3032464d3 100644 --- a/ports/mimxrt/mphalport.c +++ b/ports/mimxrt/mphalport.c @@ -75,7 +75,7 @@ int mp_hal_stdin_rx_chr(void) { return buf[0]; } } - __WFI(); + MICROPY_EVENT_POLL_HOOK } } @@ -83,17 +83,16 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { if (tud_cdc_connected()) { for (size_t i = 0; i < len;) { uint32_t n = len - i; - uint32_t n2 = tud_cdc_write(str + i, n); - if (n2 < n) { - while (!tud_cdc_write_flush()) { - __WFI(); - } + if (n > CFG_TUD_CDC_EP_BUFSIZE) { + n = CFG_TUD_CDC_EP_BUFSIZE; } + while (n > tud_cdc_write_available()) { + __WFE(); + } + uint32_t n2 = tud_cdc_write(str + i, n); + tud_cdc_write_flush(); i += n2; } - while (!tud_cdc_write_flush()) { - __WFI(); - } } // TODO // while (len--) { diff --git a/ports/mimxrt/tusb_config.h b/ports/mimxrt/tusb_config.h index c7ec05e63..862fb6c52 100644 --- a/ports/mimxrt/tusb_config.h +++ b/ports/mimxrt/tusb_config.h @@ -32,6 +32,5 @@ #define CFG_TUD_CDC (1) #define CFG_TUD_CDC_RX_BUFSIZE (512) #define CFG_TUD_CDC_TX_BUFSIZE (512) -#define CFG_TUD_CDC_EPSIZE (512) #endif // MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H diff --git a/ports/mimxrt/tusb_port.c b/ports/mimxrt/tusb_port.c index 70f8ef527..0d73b0923 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) @@ -66,11 +67,11 @@ static const tusb_desc_device_t usbd_desc_device = { }; static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { - TUD_CONFIG_DESCRIPTOR(USBD_ITF_MAX, USBD_STR_0, 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, 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[] = { @@ -89,7 +90,7 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { return usbd_desc_cfg; } -const uint16_t *tud_descriptor_string_cb(uint8_t index) { +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]; diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index e5a1b471a..868095080 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),) @@ -439,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 diff --git a/ports/nrf/README.md b/ports/nrf/README.md index b08a03456..0341cd81c 100644 --- a/ports/nrf/README.md +++ b/ports/nrf/README.md @@ -58,12 +58,13 @@ 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 submodules + make BOARD=pca10040 + make BOARD=pca10040 deploy ## Compile without LTO enabled @@ -118,26 +119,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 +175,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. @@ -187,3 +214,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 diff --git a/ports/nrf/boards/memory.ld b/ports/nrf/boards/memory.ld index f1f9a2a4c..9c4c9c8bd 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; diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md b/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md index c39a800d7..f993c7dc5 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 f98d5c88a..ca437418d 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_open_bootloader_1.2.x.ld b/ports/nrf/boards/nrf52840_open_bootloader_1.2.x.ld new file mode 100644 index 000000000..c23a2dbda --- /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; diff --git a/ports/nrf/boards/pca10059/mpconfigboard.mk b/ports/nrf/boards/pca10059/mpconfigboard.mk index ca555d393..ca437418d 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 diff --git a/ports/nrf/drivers/usb/usb_cdc.c b/ports/nrf/drivers/usb/usb_cdc.c index c90bced6b..9d7c832e2 100644 --- a/ports/nrf/drivers/usb/usb_cdc.c +++ b/ports/nrf/drivers/usb/usb_cdc.c @@ -223,4 +223,8 @@ void mp_hal_stdout_tx_strn_cooked(const char *str, mp_uint_t len) { } } +void USBD_IRQHandler(void) { + tud_int_handler(0); +} + #endif // MICROPY_HW_USB_CDC diff --git a/ports/nrf/drivers/usb/usb_descriptors.c b/ports/nrf/drivers/usb/usb_descriptors.c index 3704e5d0d..e9436ded5 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) @@ -66,11 +67,11 @@ static const tusb_desc_device_t usbd_desc_device = { }; static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { - TUD_CONFIG_DESCRIPTOR(USBD_ITF_MAX, USBD_STR_0, 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, 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[] = { @@ -89,7 +90,7 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { return usbd_desc_cfg; } -const uint16_t *tud_descriptor_string_cb(uint8_t index) { +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]; diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index afa906c35..aac932087 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 @@ -96,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 }, @@ -161,13 +161,13 @@ 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, .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/nrf/modules/machine/i2c.h b/ports/nrf/modules/machine/i2c.h index 92194ce75..1dfb1f077 100644 --- a/ports/nrf/modules/machine/i2c.h +++ b/ports/nrf/modules/machine/i2c.h @@ -27,7 +27,9 @@ #ifndef I2C_H__ #define I2C_H__ -extern const mp_obj_type_t machine_i2c_type; +#include "extmod/machine_i2c.h" + +extern const mp_obj_type_t machine_hard_i2c_type; void i2c_init0(void); diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index 48730c849..3330349cd 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(&machine_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/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 76bb6d7bd..c246c53dd 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) @@ -202,8 +201,6 @@ // type definitions for the specific machine -#define BYTES_PER_WORD (4) - #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) #define MP_SSIZE_MAX (0x7fffffff) @@ -333,8 +330,6 @@ extern const struct _mp_obj_module_t ble_module; __WFI(); \ } while (0); -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - // We need to provide a declaration/definition of alloca() #include diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 15b37b7ef..5900559b5 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/pic16bit/mpconfigport.h b/ports/pic16bit/mpconfigport.h index f121081e6..43300d176 100644 --- a/ports/pic16bit/mpconfigport.h +++ b/ports/pic16bit/mpconfigport.h @@ -84,8 +84,6 @@ typedef unsigned int mp_uint_t; // must be pointer size typedef int mp_off_t; -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - // extra builtin names to add to the global namespace #define MICROPY_PORT_BUILTINS \ { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, diff --git a/ports/powerpc/mpconfigport.h b/ports/powerpc/mpconfigport.h index 7a9277736..0b868e3da 100644 --- a/ports/powerpc/mpconfigport.h +++ b/ports/powerpc/mpconfigport.h @@ -103,8 +103,6 @@ typedef unsigned long mp_uint_t; // must be pointer size typedef long mp_off_t; -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - // extra built in names to add to the global namespace #define MICROPY_PORT_BUILTINS \ { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt new file mode 100644 index 000000000..bd58d8f70 --- /dev/null +++ b/ports/rp2/CMakeLists.txt @@ -0,0 +1,166 @@ +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() + +# Use the local tinyusb instead of the one in pico-sdk +set(PICO_TINYUSB_PATH ${MPY_DIR}/lib/tinyusb) + +# 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 + pico_unique_id + 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 000000000..08cd53dcc --- /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 000000000..d49550c5c --- /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 000000000..f8925e5aa --- /dev/null +++ b/ports/rp2/machine_adc.c @@ -0,0 +1,123 @@ +/* + * 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")); + } + } + + // Initialise the ADC peripheral if it's not already running. + if (!(adc_hw->cs & ADC_CS_EN_BITS)) { + 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 000000000..0852cc199 --- /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 000000000..0525e0f9f --- /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[NUM_BANK0_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 < NUM_BANK0_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 000000000..ff40c5503 --- /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 000000000..478c06145 --- /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 000000000..e7e8f02d5 --- /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 000000000..30446e688 --- /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 000000000..38e059701 --- /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 000000000..64218f97c --- /dev/null +++ b/ports/rp2/main.c @@ -0,0 +1,215 @@ +/* + * 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. + 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) { + ret = pyexec_file_if_exists("main.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } + } + + for (;;) { + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + if (pyexec_raw_repl() != 0) { + break; + } + } else { + if (pyexec_friendly_repl() != 0) { + break; + } + } + } + + soft_reset_exit: + 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 000000000..a56d4b1b0 --- /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 000000000..0dc96743e --- /dev/null +++ b/ports/rp2/memmap_mp.ld @@ -0,0 +1,253 @@ +/* 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 Raspberry Pi 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 : { + __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 */ + *(.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") + + 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 */ +} + diff --git a/ports/rp2/micropy_extmod.cmake b/ports/rp2/micropy_extmod.cmake new file mode 100644 index 000000000..1e968bef6 --- /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 000000000..aeb7e2b9b --- /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 000000000..9eee4ac14 --- /dev/null +++ b/ports/rp2/micropy_rules.cmake @@ -0,0 +1,90 @@ +# 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 + +add_custom_command( + OUTPUT ${MPY_MODULEDEFS} + COMMAND python3 ${MPY_PY_DIR}/makemoduledefs.py --vpath="/" ${SOURCE_QSTR} > ${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 000000000..dbaafabe8 --- /dev/null +++ b/ports/rp2/modmachine.c @@ -0,0 +1,166 @@ +/* + * 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 "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 (;;) { + __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 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) }, + + { 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 000000000..d09c83aee --- /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 000000000..8009fa33f --- /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 000000000..805c785f2 --- /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 000000000..099e5aba0 --- /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 000000000..a3adcdc51 --- /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 000000000..7ee662b5a --- /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 000000000..4835a0ee1 --- /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 000000000..b3214acfc --- /dev/null +++ b/ports/rp2/mpconfigport.h @@ -0,0 +1,198 @@ +/* + * 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_UBINASCII_CRC32 (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_PULSE (1) +#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 000000000..1122afcef --- /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 000000000..40633dcf2 --- /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 000000000..fb4428772 --- /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 000000000..4583f6f53 --- /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 000000000..472d05f43 --- /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 000000000..cd1bc6548 --- /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 000000000..7d8921d0c --- /dev/null +++ b/ports/rp2/rp2_pio.c @@ -0,0 +1,787 @@ +/* + * 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, 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_jmp_pin, 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_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} }, + { 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 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); + 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 000000000..1402edf37 --- /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 000000000..afb566bdb --- /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 000000000..b7991563a --- /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 000000000..a49172f8f --- /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 diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index ffb30bf5e..852d9e9ee 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -98,7 +98,6 @@ extern const struct _mp_obj_module_t mp_module_utime; } while (0); #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) #define MP_SSIZE_MAX (0x7fffffff) typedef int mp_int_t; // must be pointer size diff --git a/ports/samd/mphalport.c b/ports/samd/mphalport.c index 67fc2cb0d..357ec93a6 100644 --- a/ports/samd/mphalport.c +++ b/ports/samd/mphalport.c @@ -80,17 +80,16 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { if (tud_cdc_connected()) { for (size_t i = 0; i < len;) { uint32_t n = len - i; - uint32_t n2 = tud_cdc_write(str + i, n); - if (n2 < n) { - while (!tud_cdc_write_flush()) { - __WFI(); - } + if (n > CFG_TUD_CDC_EP_BUFSIZE) { + n = CFG_TUD_CDC_EP_BUFSIZE; + } + while (n > tud_cdc_write_available()) { + __WFI(); } + uint32_t n2 = tud_cdc_write(str + i, n); + tud_cdc_write_flush(); i += n2; } - while (!tud_cdc_write_flush()) { - __WFI(); - } } while (len--) { while (!(USARTx->USART.INTFLAG.bit.DRE)) { diff --git a/ports/samd/tusb_port.c b/ports/samd/tusb_port.c index 019e3a891..e96f33246 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) @@ -67,11 +68,11 @@ static const tusb_desc_device_t usbd_desc_device = { }; static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { - TUD_CONFIG_DESCRIPTOR(USBD_ITF_MAX, USBD_STR_0, 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, 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[] = { @@ -90,7 +91,7 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { return usbd_desc_cfg; } -const uint16_t *tud_descriptor_string_cb(uint8_t index) { +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]; @@ -117,29 +118,29 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index) { #if defined(MCU_SAMD21) void USB_Handler_wrapper(void) { - USB_Handler(); + tud_int_handler(0); tud_task(); } #elif defined(MCU_SAMD51) void USB_0_Handler_wrapper(void) { - USB_0_Handler(); + tud_int_handler(0); tud_task(); } void USB_1_Handler_wrapper(void) { - USB_1_Handler(); + tud_int_handler(0); tud_task(); } void USB_2_Handler_wrapper(void) { - USB_2_Handler(); + tud_int_handler(0); tud_task(); } void USB_3_Handler_wrapper(void) { - USB_3_Handler(); + tud_int_handler(0); tud_task(); } diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 7b68299e0..6e5fc1536 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 @@ -133,15 +134,24 @@ 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 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 -LIB_SRC_C = $(addprefix lib/,\ +LIB_SRC_C += $(addprefix lib/,\ libc/string0.c \ mp-readline/readline.c \ netutils/netutils.c \ @@ -156,7 +166,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 \ @@ -206,7 +216,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 \ @@ -248,18 +258,19 @@ 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 \ dht/dht.c \ ) -SRC_C = \ +SRC_C += \ + boardctrl.c \ main.c \ stm32_it.c \ usbd_conf.c \ @@ -330,7 +341,10 @@ SRC_C = \ adc.c \ $(wildcard $(BOARD_DIR)/*.c) -SRC_O = \ +SRC_CXX += \ + $(SRC_MOD_CXX) + +SRC_O += \ $(STARTUP_FILE) \ $(SYSTEM_FILE) @@ -352,7 +366,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 \ @@ -377,13 +391,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 \ @@ -391,7 +405,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 \ @@ -400,14 +414,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 \ @@ -419,7 +433,6 @@ SRC_USBDEV = $(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) @@ -489,6 +502,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 @@ -505,16 +519,16 @@ endif endif -OBJ = OBJ += $(PY_O) 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 @@ -533,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: @@ -553,14 +573,65 @@ CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool CFLAGS += -DMICROPY_MODULE_FROZEN_MPY endif -.PHONY: deploy - -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 $< +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_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) +endef + +.PHONY: deploy deploy-stlink deploy-openocd + +ifeq ($(MBOOT_ENABLE_PACKING),1) +deploy: $(BUILD)/firmware.pack.dfu + $(call RUN_DFU,$^) else - $(Q)$(DFU_UTIL) -a 0 -d $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) -D $< +deploy: $(BUILD)/firmware.dfu + $(call RUN_DFU,$^) endif # A board should specify TEXT0_ADDR if to use a different location than the @@ -573,18 +644,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.bin + $(call RUN_OPENOCD,$^,$(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)" +$(BUILD)/firmware.bin: $(BUILD)/firmware.elf + $(call GENERATE_BIN,$@,$^,$(TEXT0_SECTIONS)) -$(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.dfu: $(BUILD)/firmware.bin + $(call GENERATE_DFU,$@,$^,$(TEXT0_ADDR)) else # TEXT0_ADDR and TEXT1_ADDR are specified so split firmware between these locations @@ -592,38 +662,31 @@ 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.pack.dfu: $(BUILD)/firmware.dfu $(MBOOT_PACK_KEYS_FILE) + $(call GENERATE_PACK_DFU,$@,$<) + $(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 @@ -645,7 +708,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/adc.c b/ports/stm32/adc.c index 3793ef5ff..5fa22d7c7 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 4ae6022bc..6f2e61e09 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/boardctrl.c b/ports/stm32/boardctrl.c new file mode 100644 index 000000000..188068d70 --- /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 000000000..6f79dfb89 --- /dev/null +++ b/ports/stm32/boardctrl.h @@ -0,0 +1,84 @@ +/* + * 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_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 + +#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/boards/B_L072Z_LRWAN1/mpconfigboard.h b/ports/stm32/boards/B_L072Z_LRWAN1/mpconfigboard.h index 531794de6..b330e8fdc 100644 --- a/ports/stm32/boards/B_L072Z_LRWAN1/mpconfigboard.h +++ b/ports/stm32/boards/B_L072Z_LRWAN1/mpconfigboard.h @@ -13,6 +13,7 @@ #define MICROPY_PY_FRAMEBUF (0) #define MICROPY_PY_USOCKET (0) #define MICROPY_PY_NETWORK (0) +#define MICROPY_PY_ONEWIRE (0) #define MICROPY_PY_STM (0) #define MICROPY_PY_PYB_LEGACY (0) #define MICROPY_PY_UHEAPQ (0) diff --git a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h index 4e6b16226..324f2b03c 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) diff --git a/ports/stm32/boards/NUCLEO_L073RZ/mpconfigboard.h b/ports/stm32/boards/NUCLEO_L073RZ/mpconfigboard.h index 2b01bd33a..a5c5dc9a0 100644 --- a/ports/stm32/boards/NUCLEO_L073RZ/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_L073RZ/mpconfigboard.h @@ -13,6 +13,7 @@ #define MICROPY_PY_FRAMEBUF (0) #define MICROPY_PY_USOCKET (0) #define MICROPY_PY_NETWORK (0) +#define MICROPY_PY_ONEWIRE (0) #define MICROPY_PY_STM (0) #define MICROPY_PY_PYB_LEGACY (0) #define MICROPY_PY_UHEAPQ (0) diff --git a/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h b/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h index 6a3a364dc..14b3bab8a 100644 --- a/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_L432KC/mpconfigboard.h @@ -7,6 +7,7 @@ #define MICROPY_PY_GENERATOR_PEND_THROW (0) #define MICROPY_PY_USOCKET (0) #define MICROPY_PY_NETWORK (0) +#define MICROPY_PY_ONEWIRE (0) #define MICROPY_PY_STM (0) #define MICROPY_PY_PYB_LEGACY (0) #define MICROPY_PY_UHEAPQ (0) 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 000000000..773813079 --- /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.h b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h index 206134941..179369d94 100644 --- a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.h @@ -22,6 +22,8 @@ // UART buses #define MICROPY_HW_UART1_TX (pin_B6) #define MICROPY_HW_UART1_RX (pin_B7) +#define MICROPY_HW_LPUART1_TX (pin_A2) +#define MICROPY_HW_LPUART1_RX (pin_A3) // USART 1 is connected to the virtual com port on the ST-LINK #define MICROPY_HW_UART_REPL PYB_UART_1 #define MICROPY_HW_UART_REPL_BAUD 115200 diff --git a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk index dcec788ed..4cb047c95 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 diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore.py b/ports/stm32/boards/NUCLEO_WB55/rfcore.py index cfe315605..e612499a7 100644 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore.py +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore.py @@ -34,15 +34,10 @@ # to print out SRAM2A, register state and FUS/WS info. from machine import mem8, mem16, mem32 -import time, struct +import time, struct, uctypes import stm -def addressof(buf): - assert type(buf) is bytearray - return mem32[id(buf) + 12] - - class Flash: FLASH_KEY1 = 0x45670123 FLASH_KEY2 = 0xCDEF89AB @@ -73,7 +68,7 @@ def write(self, addr, buf): self.wait_not_busy() cr = 1 << 0 # PG mem32[stm.FLASH + stm.FLASH_CR] = cr - buf_addr = addressof(buf) + buf_addr = uctypes.addressof(buf) off = 0 while off < len(buf): mem32[addr + off] = mem32[buf_addr + off] @@ -116,6 +111,13 @@ def copy_file_to_flash(filename, addr): 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) @@ -197,9 +199,6 @@ 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/boards/NUCLEO_WB55/rfcore_debug.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py new file mode 100644 index 000000000..4dbead0ac --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py @@ -0,0 +1,237 @@ +# 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. + +# 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 stm + +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) + +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) + + # 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)) + + +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() +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 000000000..b5f1d0072 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py @@ -0,0 +1,559 @@ +# 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 + +# 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)) + + +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] & _Flash._FLASH_SR_BSY_MASK: + machine.idle() + + def unlock(self): + 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] = _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 | _Flash._FLASH_CR_STRT_MASK + 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"", timeout=0): + try: + ogf_out, ocf_out, status, result = _parse_vendor_response( + stm.rfcore_sys_hci(ogf, ocf, buf, timeout) + ) + 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(timeout=0): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_GET_STATE, timeout=timeout) + + +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 == _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 == _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 == _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 == _STATE_INSTALLING_FUS: + log("Installing FUS...") + 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 + 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) + 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 == _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 == _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 == _STATE_INSTALLING_WS: + log("Installing WS...") + 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 + 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 000000000..23f3d20f0 --- /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("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_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index f6e130eb5..dc42751ff 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -77,6 +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) : \ @@ -182,6 +185,7 @@ extern struct _spi_bdev_t spi_bdev2; // Bluetooth config #define MICROPY_HW_BLE_UART_ID (PYB_UART_6) #define MICROPY_HW_BLE_UART_BAUDRATE (115200) +#define MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY (3000000) /******************************************************************************/ // Bootloader configuration diff --git a/ports/stm32/boards/PYBD_SF6/f767.ld b/ports/stm32/boards/PYBD_SF6/f767.ld index 1dd4c11ed..5866f0b5c 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 5f920b417..b6957a321 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/STM32F769DISC/mpconfigboard.h b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h index e1fe93bb0..843b987ce 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 283118753..ad62f761e 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/boards/common_basic.ld b/ports/stm32/boards/common_basic.ld index 2e428aa62..dbda1b8b6 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 52b2a677d..21d809a3d 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 65722f2e5..5517a2d09 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 000000000..1bb2249e9 --- /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 000000000..eb9b86f49 --- /dev/null +++ b/ports/stm32/boards/common_extratext_data_in_flash.ld @@ -0,0 +1,22 @@ +/* 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); + +/* 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 000000000..aba6bf57c --- /dev/null +++ b/ports/stm32/boards/common_extratext_data_in_flash_app.ld @@ -0,0 +1,22 @@ +/* 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); + +/* 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 000000000..5a29e4730 --- /dev/null +++ b/ports/stm32/boards/common_extratext_data_in_flash_text.ld @@ -0,0 +1,22 @@ +/* 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); + +/* 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 74b2ffb41..733ca12f6 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 } diff --git a/ports/stm32/boards/make-pins.py b/ports/stm32/boards/make-pins.py index a91ed8a2c..a19532331 100755 --- a/ports/stm32/boards/make-pins.py +++ b/ports/stm32/boards/make-pins.py @@ -14,6 +14,7 @@ "I2S": ["CK", "MCK", "SD", "WS", "EXTSD"], "USART": ["RX", "TX", "CTS", "RTS", "CK"], "UART": ["RX", "TX", "CTS", "RTS"], + "LPUART": ["RX", "TX", "CTS", "RTS"], "SPI": ["NSS", "SCK", "MISO", "MOSI"], "SDMMC": ["CK", "CMD", "D0", "D1", "D2", "D3"], "CAN": ["TX", "RX"], @@ -24,6 +25,7 @@ "I2S": "MICROPY_HW_ENABLE_I2S{num}", "SPI": "MICROPY_HW_SPI{num}_SCK", "UART": "MICROPY_HW_UART{num}_TX", + "LPUART": "MICROPY_HW_LPUART{num}_TX", "USART": "MICROPY_HW_UART{num}_TX", "SDMMC": "MICROPY_HW_SDMMC{num}_CK", "CAN": "MICROPY_HW_CAN{num}_TX", diff --git a/ports/stm32/boards/stm32f0xx_hal_conf_base.h b/ports/stm32/boards/stm32f0xx_hal_conf_base.h index 9cb7761ac..70e6ccf75 100644 --- a/ports/stm32/boards/stm32f0xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f0xx_hal_conf_base.h @@ -47,6 +47,9 @@ #include "stm32f0xx_hal_uart.h" #include "stm32f0xx_hal_usart.h" #include "stm32f0xx_hal_wwdg.h" +#include "stm32f0xx_ll_adc.h" +#include "stm32f0xx_ll_rtc.h" +#include "stm32f0xx_ll_usart.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 cdae0c562..134a30018 100644 --- a/ports/stm32/boards/stm32f4xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f4xx_hal_conf_base.h @@ -53,6 +53,10 @@ #include "stm32f4xx_hal_uart.h" #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" +#include "stm32f4xx_ll_usart.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 05ab10fea..efb15d471 100644 --- a/ports/stm32/boards/stm32f7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f7xx_hal_conf_base.h @@ -53,6 +53,10 @@ #include "stm32f7xx_hal_uart.h" #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" +#include "stm32f7xx_ll_usart.h" // Enable various HAL modules #define HAL_ADC_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32h743.ld b/ports/stm32/boards/stm32h743.ld index 69738ab8b..8cf8a4e59 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 +} diff --git a/ports/stm32/boards/stm32h7xx_hal_conf_base.h b/ports/stm32/boards/stm32h7xx_hal_conf_base.h index 5c97e2c44..c07ae93e3 100644 --- a/ports/stm32/boards/stm32h7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32h7xx_hal_conf_base.h @@ -53,6 +53,10 @@ #include "stm32h7xx_hal_uart.h" #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" +#include "stm32h7xx_ll_usart.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 ed524fecc..cc033666a 100644 --- a/ports/stm32/boards/stm32l0xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32l0xx_hal_conf_base.h @@ -46,6 +46,9 @@ #include "stm32l0xx_hal_uart.h" #include "stm32l0xx_hal_usart.h" #include "stm32l0xx_hal_wwdg.h" +#include "stm32l0xx_ll_adc.h" +#include "stm32l0xx_ll_rtc.h" +#include "stm32l0xx_ll_usart.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 cfffcffbb..8d77a80a7 100644 --- a/ports/stm32/boards/stm32l4xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32l4xx_hal_conf_base.h @@ -51,6 +51,8 @@ #include "stm32l4xx_hal_usart.h" #include "stm32l4xx_hal_wwdg.h" #include "stm32l4xx_ll_adc.h" +#include "stm32l4xx_ll_rtc.h" +#include "stm32l4xx_ll_usart.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 8dbc9ecea..72af0262a 100644 --- a/ports/stm32/boards/stm32wbxx_hal_conf_base.h +++ b/ports/stm32/boards/stm32wbxx_hal_conf_base.h @@ -41,6 +41,9 @@ #include "stm32wbxx_hal_tim.h" #include "stm32wbxx_hal_uart.h" #include "stm32wbxx_hal_usart.h" +#include "stm32wbxx_ll_adc.h" +#include "stm32wbxx_ll_rtc.h" +#include "stm32wbxx_ll_usart.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/can.c b/ports/stm32/can.c index e5fc55e40..bcc698359 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/ports/stm32/eth.c b/ports/stm32/eth.c index 33d20707e..7a08e8c28 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); diff --git a/ports/stm32/flash.c b/ports/stm32/flash.c index 499129a6f..d399ece86 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/i2c.c b/ports/stm32/i2c.c index 5981df11c..0b7105344 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,59 @@ 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); + } + } + + // 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; +} + #endif // MICROPY_HW_ENABLE_HW_I2C diff --git a/ports/stm32/i2c.h b/ports/stm32/i2c.h index 4846f1cf3..d494ad66d 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_adc.c b/ports/stm32/machine_adc.c index f29896d37..d9e5a64da 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) @@ -156,10 +157,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)) { } } @@ -329,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 diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index 14dd88b78..41e65cf05 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) @@ -194,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[] = { @@ -209,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 @@ -266,13 +236,13 @@ 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, .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/stm32/machine_spi.c b/ports/stm32/machine_spi.c index cf6e96ab6..37c026cef 100644 --- a/ports/stm32/machine_spi.c +++ b/ports/stm32/machine_spi.c @@ -46,9 +46,11 @@ 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_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 +139,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/machine_uart.c b/ports/stm32/machine_uart.c index 2862f4c0e..eceb5b6e7 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -76,7 +76,14 @@ 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) { - mp_printf(print, "UART(%u)", self->uart_id); + #ifdef LPUART1 + if (self->uart_id == PYB_LPUART_1) { + mp_printf(print, "UART('LP1')"); + } else + #endif + { + mp_printf(print, "UART(%u)", self->uart_id); + } } else { mp_int_t bits; uint32_t cr1 = self->uartx->CR1; @@ -98,8 +105,16 @@ STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k if (cr1 & USART_CR1_PCE) { bits -= 1; } - mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=", - self->uart_id, uart_get_baudrate(self), bits); + #ifdef LPUART1 + if (self->uart_id == PYB_LPUART_1) { + mp_printf(print, "UART('LP1', baudrate=%u, bits=%u, parity=", + uart_get_baudrate(self), bits); + } else + #endif + { + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=", + self->uart_id, uart_get_baudrate(self), bits); + } if (!(cr1 & USART_CR1_PCE)) { mp_print_str(print, "None"); } else if (!(cr1 & USART_CR1_PS)) { @@ -335,6 +350,14 @@ STATIC mp_obj_t pyb_uart_make_new(const mp_obj_type_t *type, size_t n_args, size } else if (strcmp(port, MICROPY_HW_UART10_NAME) == 0) { uart_id = PYB_UART_10; #endif + #ifdef MICROPY_HW_LPUART1_NAME + } else if (strcmp(port, MICROPY_HW_LPUART1_NAME) == 0) { + uart_id = PYB_LPUART_1; + #endif + #ifdef LPUART1 + } else if (strcmp(port, "LP1") == 0 && uart_exists(PYB_LPUART_1)) { + uart_id = PYB_LPUART_1; + #endif } else { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%s) doesn't exist"), port); } @@ -345,6 +368,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/main.c b/ports/stm32/main.c index b5dbfa50f..38710e265 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++) { } @@ -133,6 +122,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; @@ -160,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) { @@ -171,34 +164,39 @@ 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); - #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 - - // Try to detect the block device used for the main filesystem, based on the first block + int ret; - uint8_t buf[FLASH_BLOCK_SIZE]; - storage_read_blocks(buf, FLASH_PART1_START_BLOCK, 1); + #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 + // 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; + break; + } + #endif - #if MICROPY_VFS_LFS1 - if (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; + #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 MICROPY_VFS_LFS2 - if (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; - } - #endif if (len != -1) { // Detected a littlefs filesystem so create correct block device for it @@ -210,7 +208,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), @@ -306,83 +304,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 @@ -469,10 +390,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) @@ -520,8 +438,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 @@ -548,22 +466,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 -soft_reset: + boardctrl_state_t state; + state.reset_mode = reset_mode; + state.run_boot_py = false; + state.run_main_py = false; + state.last_ret = 0; - #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_BEFORE_SOFT_RESET_LOOP(&state); - #if !MICROPY_HW_USES_BOOTLOADER - // check if user switch held to select the reset mode - reset_mode = update_reset_mode(1); - #endif +soft_reset: + + MICROPY_BOARD_TOP_SOFT_RESET_LOOP(&state); // Python threading init #if MICROPY_PY_THREAD @@ -617,7 +530,7 @@ void stm32_main(uint32_t reset_mode) { // 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 @@ -652,29 +565,19 @@ void stm32_main(uint32_t reset_mode) { // reset config variables; they should be set by boot.py MP_STATE_PORT(pyb_config_main) = MP_OBJ_NULL; + MICROPY_BOARD_BEFORE_BOOT_PY(&state); + // run boot.py, if it exists // TODO perhaps have pyb.reboot([bootpy]) function to soft-reboot and execute custom boot.py - if (reset_mode == 1 || reset_mode == 3) { + if (state.run_boot_py) { const char *boot_py = "boot.py"; - int ret = pyexec_file_if_exists(boot_py); - if (ret & PYEXEC_FORCED_EXIT) { + state.last_ret = pyexec_file_if_exists(boot_py); + if (state.last_ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; } - if (!ret) { - flash_error(4); - } } - // turn boot-up LEDs off - #if !defined(MICROPY_HW_LED2) - // If there is only one LED on the board then it's used to signal boot-up - // and so we turn it off here. Otherwise LED(1) is used to indicate dirty - // flash cache and so we shouldn't change its state. - led_state(1, 0); - #endif - led_state(2, 0); - led_state(3, 0); - led_state(4, 0); + MICROPY_BOARD_AFTER_BOOT_PY(&state); // Now we initialise sub-systems that need configuration from boot.py, // or whose initialisation can be safely deferred until after running @@ -709,23 +612,24 @@ void stm32_main(uint32_t reset_mode) { // At this point everything is fully configured and initialised. + MICROPY_BOARD_BEFORE_MAIN_PY(&state); + // Run the main script from the current directory. - if ((reset_mode == 1 || reset_mode == 3) && pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { + if (state.run_main_py) { const char *main_py; if (MP_STATE_PORT(pyb_config_main) == MP_OBJ_NULL) { main_py = "main.py"; } else { main_py = mp_obj_str_get_str(MP_STATE_PORT(pyb_config_main)); } - int ret = pyexec_file_if_exists(main_py); - if (ret & PYEXEC_FORCED_EXIT) { + state.last_ret = pyexec_file_if_exists(main_py); + if (state.last_ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; } - if (!ret) { - flash_error(3); - } } + MICROPY_BOARD_AFTER_MAIN_PY(&state); + #if MICROPY_ENABLE_COMPILER // Main script is finished, so now go into REPL mode. // The REPL mode can change, or it can request a soft reset. @@ -746,12 +650,19 @@ void stm32_main(uint32_t reset_mode) { // soft reset + MICROPY_BOARD_START_SOFT_RESET(&state); + #if MICROPY_HW_ENABLE_STORAGE - printf("MPY: sync filesystems\n"); + if (state.log_soft_reset) { + mp_printf(&mp_plat_print, "MPY: sync filesystems\n"); + } storage_flush(); #endif - printf("MPY: soft reboot\n"); + if (state.log_soft_reset) { + mp_printf(&mp_plat_print, "MPY: soft reboot\n"); + } + #if MICROPY_PY_BLUETOOTH mp_bluetooth_deinit(); #endif @@ -770,6 +681,8 @@ void stm32_main(uint32_t reset_mode) { pyb_thread_deinit(); #endif + MICROPY_BOARD_END_SOFT_RESET(&state); + gc_sweep_all(); goto soft_reset; diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index c901dfb33..0c9244259 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)) @@ -71,8 +77,9 @@ 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) LDFLAGS = -nostdlib -L . -T stm32_generic.ld -Map=$(@:.elf=.map) --cref @@ -85,7 +92,7 @@ LDFLAGS += --gc-sections # Debugging/Optimization ifeq ($(DEBUG), 1) CFLAGS += -g -DPENDSV_DEBUG -COPT = -O0 +COPT = -Og else COPT += -Os -DNDEBUG endif @@ -109,6 +116,7 @@ SRC_C = \ elem.c \ fsload.c \ gzstream.c \ + pack.c \ vfs_fat.c \ vfs_lfs.c \ drivers/bus/softspi.c \ @@ -128,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 000000000..5e0953739 --- /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 d8aa6d456..59213eb61 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 a1d4d10d0..73db3545d 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 1e1ad7a04..9ecc25b0b 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -28,49 +28,85 @@ #include "py/mphal.h" #include "mboot.h" +#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) #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; + 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 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; + 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 @@ -82,9 +118,9 @@ 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; + return -MBOOT_ERRNO_DFU_READ_ERROR; } if (write_to_flash) { res = do_write(elem_addr, buf, l); if (res != 0) { - return -1; + return res; } elem_addr += l; } @@ -127,17 +165,17 @@ static int fsload_program_file(bool write_to_flash) { } if (target_size != file_offset - file_offset_target) { - return -1; + return -MBOOT_ERRNO_DFU_INVALID_SIZE; } if (total_size != file_offset) { - return -1; + return -MBOOT_ERRNO_DFU_INVALID_SIZE; } // Read trailing info - res = gz_stream_read(16, buf); + res = input_stream_read(16, buf); if (res != 16) { - return -1; + return -MBOOT_ERRNO_DFU_READ_ERROR; } // TODO validate CRC32 @@ -151,7 +189,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); } @@ -167,7 +205,7 @@ static int fsload_validate_and_program_file(void *stream, const stream_methods_t int fsload_process(void) { const uint8_t *elem = elem_search(ELEM_DATA_START, ELEM_TYPE_FSLOAD); if (elem == NULL || elem[-1] < 2) { - return -1; + return -MBOOT_ERRNO_FSLOAD_NO_FSLOAD; } // Get mount point id and create null-terminated filename @@ -180,9 +218,20 @@ 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 - return -1; + if (elem == NULL) { + // End of elements. + return -MBOOT_ERRNO_FSLOAD_NO_MOUNT; + } + 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 -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; } if (elem[0] == mount_point) { uint32_t base_addr = get_le32(&elem[2]); @@ -202,25 +251,26 @@ 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 { // Unknown filesystem type - return -1; + return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; } if (ret == 0) { diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index dab5fa663..d7c8f46db 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_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,24 +163,36 @@ 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): - # Check firmware is of .dfu.gz type +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: 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 - 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(" + +#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]; + +// Flag to indicate that firmware_head contains valid data. +static bool firmware_head_valid; + +void mboot_pack_init(void) { + erased_base_addr = 0; + erased_top_addr = 0; + firmware_chunk_base_addr = 0; + firmware_head_valid = false; +} + +// 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); + firmware_head_valid = true; + } + + // 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 -MBOOT_ERRNO_PACK_INVALID_CHUNK; + } + + 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 && firmware_head_valid) { + // 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 -MBOOT_ERRNO_PACK_SIGN_FAILED; + } + 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 -MBOOT_ERRNO_PACK_SIGN_FAILED; + } + + // Full firmware passed the signature check. + + if (firmware_head_valid) { + // Write the start of the firmware so it boots. + ret = hw_write(APPLICATION_ADDR, firmware_head, sizeof(firmware_head)); + } + + return ret; +} + +// 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 -MBOOT_ERRNO_PACK_DECRYPT_FAILED; + } + + // 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 -MBOOT_ERRNO_GUNZIP_FAILED; // 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 -MBOOT_ERRNO_PACK_INVALID_ADDR; + } + + 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 -MBOOT_ERRNO_PACK_INVALID_ADDR; + } + + // 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 -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 -MBOOT_ERRNO_PACK_INVALID_ADDR; + } + + 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 -MBOOT_ERRNO_PACK_SIGN_FAILED; + } + + // 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 -MBOOT_ERRNO_PACK_INVALID_CHUNK; + } +} + +#endif // MBOOT_ENABLE_PACKING diff --git a/ports/stm32/mboot/pack.h b/ports/stm32/mboot/pack.h new file mode 100644 index 000000000..195f297ca --- /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 0504d6d6a..ade434967 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.h b/ports/stm32/mboot/vfs.h index 6cf883a13..22bb98936 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_fat.c b/ports/stm32/mboot/vfs_fat.c index 20de074f0..cfa30fb12 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; } @@ -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 dec7c015f..e7fd8ce63 100644 --- a/ports/stm32/mboot/vfs_lfs.c +++ b/ports/stm32/mboot/vfs_lfs.c @@ -37,32 +37,30 @@ #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 #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]; #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 #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]; @@ -72,7 +70,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); @@ -90,23 +88,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]; - do_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; @@ -140,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; } @@ -150,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; } diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index ac4d87123..f9fd1d9a6 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" @@ -154,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 { @@ -403,7 +406,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { 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) }, + #if MICROPY_PY_MACHINE_PULSE { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, @@ -415,9 +420,17 @@ 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) }, + #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 + #if MICROPY_PY_MACHINE_SPI { 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) }, + #endif { 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 e4ccf6388..e84620854 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/modstm.c b/ports/stm32/modstm.c index 418b8bde2..3f4f33979 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/modutime.c b/ports/stm32/modutime.c index 2a37a130d..4bce45eb3 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/stm32/mpbthciport.c b/ports/stm32/mpbthciport.c index a5977ff12..c3cd864e2 100644 --- a/ports/stm32/mpbthciport.c +++ b/ports/stm32/mpbthciport.c @@ -27,31 +27,65 @@ #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" -#include "py/obj.h" - #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 +101,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 +169,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 +182,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,14 +195,25 @@ 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. - mp_bluetooth_hci_uart_set_baudrate(baudrate); + // Initialise the UART. + 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); + + // 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); 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; @@ -162,22 +221,7 @@ int mp_bluetooth_hci_uart_deinit(void) { 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); - - // 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); - + uart_set_baudrate(&mp_bluetooth_hci_uart_obj, baudrate); return 0; } @@ -188,7 +232,7 @@ int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t 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); + mp_printf(&mp_plat_print, "\nmp_bluetooth_hci_uart_write: failed to write to UART %d\n", errcode); } return 0; } diff --git a/ports/stm32/mpbtstackport.c b/ports/stm32/mpbtstackport.c index a031a6a15..3dfd8cb3c 100644 --- a/ports/stm32/mpbtstackport.c +++ b/ports/stm32/mpbtstackport.c @@ -58,7 +58,7 @@ uint32_t hal_time_ms(void) { STATIC const hci_transport_config_uart_t hci_transport_config_uart = { HCI_TRANSPORT_CONFIG_UART, MICROPY_HW_BLE_UART_BAUDRATE, - 3000000, + MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY, 0, NULL, }; @@ -90,9 +90,9 @@ void mp_bluetooth_btstack_port_init(void) { 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()); + #ifdef MICROPY_HW_BLE_BTSTACK_CHIPSET_INSTANCE + hci_set_chipset(MICROPY_HW_BLE_BTSTACK_CHIPSET_INSTANCE); + #endif } void mp_bluetooth_btstack_port_deinit(void) { diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 127696b19..948897b21 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -37,6 +37,11 @@ #define MICROPY_PY_STM (1) #endif +// Whether to include the pyb module +#ifndef MICROPY_PY_PYB +#define MICROPY_PY_PYB (1) +#endif + // Whether to include legacy functions and classes in the pyb module #ifndef MICROPY_PY_PYB_LEGACY #define MICROPY_PY_PYB_LEGACY (1) @@ -122,11 +127,41 @@ #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" #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 @@ -146,6 +181,7 @@ #define MICROPY_HW_MAX_I2C (2) #define MICROPY_HW_MAX_TIMER (17) #define MICROPY_HW_MAX_UART (8) +#define MICROPY_HW_MAX_LPUART (0) // Configuration for STM32F4 series #elif defined(STM32F4) @@ -165,6 +201,7 @@ #else #define MICROPY_HW_MAX_UART (6) #endif +#define MICROPY_HW_MAX_LPUART (0) // Configuration for STM32F7 series #elif defined(STM32F7) @@ -179,6 +216,7 @@ #define MICROPY_HW_MAX_I2C (4) #define MICROPY_HW_MAX_TIMER (17) #define MICROPY_HW_MAX_UART (8) +#define MICROPY_HW_MAX_LPUART (0) // Configuration for STM32H7 series #elif defined(STM32H7) @@ -188,6 +226,7 @@ #define MICROPY_HW_MAX_I2C (4) #define MICROPY_HW_MAX_TIMER (17) #define MICROPY_HW_MAX_UART (8) +#define MICROPY_HW_MAX_LPUART (1) // Configuration for STM32L0 series #elif defined(STM32L0) @@ -197,6 +236,7 @@ #define MICROPY_HW_MAX_I2C (3) #define MICROPY_HW_MAX_TIMER (22) #define MICROPY_HW_MAX_UART (5) +#define MICROPY_HW_MAX_LPUART (1) // Configuration for STM32L4 series #elif defined(STM32L4) @@ -205,7 +245,8 @@ #define PYB_EXTI_NUM_VECTORS (23) #define MICROPY_HW_MAX_I2C (4) #define MICROPY_HW_MAX_TIMER (17) -#define MICROPY_HW_MAX_UART (6) +#define MICROPY_HW_MAX_UART (5) +#define MICROPY_HW_MAX_LPUART (1) // Configuration for STM32WB series #elif defined(STM32WB) @@ -215,6 +256,31 @@ #define MICROPY_HW_MAX_I2C (3) #define MICROPY_HW_MAX_TIMER (17) #define MICROPY_HW_MAX_UART (1) +#define MICROPY_HW_MAX_LPUART (1) + +#ifndef MICROPY_HW_STM32WB_FLASH_SYNCRONISATION +#define MICROPY_HW_STM32WB_FLASH_SYNCRONISATION (1) +#endif + +// RF core BLE configuration (a board should define +// MICROPY_HW_RFCORE_BLE_NUM_GATT_ATTRIBUTES to override all values) +#ifndef MICROPY_HW_RFCORE_BLE_NUM_GATT_ATTRIBUTES +#define MICROPY_HW_RFCORE_BLE_NUM_GATT_ATTRIBUTES (0) +#define MICROPY_HW_RFCORE_BLE_NUM_GATT_SERVICES (0) +#define MICROPY_HW_RFCORE_BLE_ATT_VALUE_ARRAY_SIZE (0) +#define MICROPY_HW_RFCORE_BLE_NUM_LINK (1) +#define MICROPY_HW_RFCORE_BLE_DATA_LENGTH_EXTENSION (1) +#define MICROPY_HW_RFCORE_BLE_PREPARE_WRITE_LIST_SIZE (0) +#define MICROPY_HW_RFCORE_BLE_MBLOCK_COUNT (0x79) +#define MICROPY_HW_RFCORE_BLE_MAX_ATT_MTU (0) +#define MICROPY_HW_RFCORE_BLE_SLAVE_SCA (0) +#define MICROPY_HW_RFCORE_BLE_MASTER_SCA (0) +#define MICROPY_HW_RFCORE_BLE_LSE_SOURCE (0) // use LSE to clock the rfcore (see errata 2.2.1) +#define MICROPY_HW_RFCORE_BLE_MAX_CONN_EVENT_LENGTH (0xffffffff) +#define MICROPY_HW_RFCORE_BLE_HSE_STARTUP_TIME (0x148) +#define MICROPY_HW_RFCORE_BLE_VITERBI_MODE (1) +#define MICROPY_HW_RFCORE_BLE_LL_ONLY (1) // use LL only, we provide the rest of the BLE stack +#endif #else #error Unsupported MCU series @@ -257,6 +323,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/mpconfigport.h b/ports/stm32/mpconfigport.h index 95b539603..174ef22f2 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) @@ -164,32 +165,38 @@ #define MICROPY_PY_UCRYPTOLIB (MICROPY_PY_USSL) #ifndef MICROPY_PY_UBINASCII #define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UBINASCII_CRC32 (1) #endif +#ifndef MICROPY_PY_UOS +#define MICROPY_PY_UOS (1) +#endif +#define MICROPY_PY_OS_DUPTERM (3) +#define MICROPY_PY_UOS_DUPTERM_BUILTIN_STREAM (1) #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) #endif #define MICROPY_PY_USELECT (1) +#ifndef MICROPY_PY_UTIME +#define MICROPY_PY_UTIME (1) +#endif +#define MICROPY_PY_UTIME_MP_HAL (MICROPY_PY_UTIME) #ifndef MICROPY_PY_UTIMEQ #define MICROPY_PY_UTIMEQ (1) #endif -#define MICROPY_PY_UTIME_MP_HAL (1) -#define MICROPY_PY_OS_DUPTERM (3) -#define MICROPY_PY_UOS_DUPTERM_BUILTIN_STREAM (1) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) +#ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) #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) -#define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hard_spi_make_new +#endif #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (HAL_RCC_GetSysClockFreq() / 48) #define MICROPY_PY_UWEBSOCKET (MICROPY_PY_LWIP) @@ -203,6 +210,9 @@ #ifndef MICROPY_PY_NETWORK #define MICROPY_PY_NETWORK (1) #endif +#ifndef MICROPY_PY_ONEWIRE +#define MICROPY_PY_ONEWIRE (1) +#endif // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) @@ -248,12 +258,38 @@ extern const struct _mp_obj_module_t mp_module_usocket; extern const struct _mp_obj_module_t mp_module_network; extern const struct _mp_obj_module_t mp_module_onewire; +#if MICROPY_PY_PYB +#define PYB_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_pyb), MP_ROM_PTR(&pyb_module) }, +#else +#define PYB_BUILTIN_MODULE +#endif + #if MICROPY_PY_STM -#define STM_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_stm), MP_ROM_PTR(&stm_module) }, +#define STM_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_stm), MP_ROM_PTR(&stm_module) }, #else #define STM_BUILTIN_MODULE #endif +#if MICROPY_PY_MACHINE +#define MACHINE_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&machine_module) }, +#define MACHINE_BUILTIN_MODULE_CONSTANTS { MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, +#else +#define MACHINE_BUILTIN_MODULE +#define MACHINE_BUILTIN_MODULE_CONSTANTS +#endif + +#if MICROPY_PY_UOS +#define UOS_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos) }, +#else +#define UOS_BUILTIN_MODULE +#endif + +#if MICROPY_PY_UTIME +#define UTIME_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_utime) }, +#else +#define UTIME_BUILTIN_MODULE +#endif + #if MICROPY_PY_USOCKET && MICROPY_PY_LWIP // usocket implementation provided by lwIP #define SOCKET_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_lwip) }, @@ -271,21 +307,27 @@ extern const struct _mp_obj_module_t mp_module_onewire; #define NETWORK_BUILTIN_MODULE #endif +#if MICROPY_PY_ONEWIRE +#define ONEWIRE_BUILTIN_MODULE { MP_ROM_QSTR(MP_QSTR__onewire), MP_ROM_PTR(&mp_module_onewire) }, +#else +#define ONEWIRE_BUILTIN_MODULE +#endif + #define MICROPY_PORT_BUILTIN_MODULES \ - { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&machine_module) }, \ - { MP_ROM_QSTR(MP_QSTR_pyb), MP_ROM_PTR(&pyb_module) }, \ + MACHINE_BUILTIN_MODULE \ + PYB_BUILTIN_MODULE \ STM_BUILTIN_MODULE \ - { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos) }, \ - { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_utime) }, \ + UOS_BUILTIN_MODULE \ + UTIME_BUILTIN_MODULE \ SOCKET_BUILTIN_MODULE \ NETWORK_BUILTIN_MODULE \ - { MP_ROM_QSTR(MP_QSTR__onewire), MP_ROM_PTR(&mp_module_onewire) }, \ + ONEWIRE_BUILTIN_MODULE \ // extra constants #define MICROPY_PORT_CONSTANTS \ - { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&machine_module) }, \ - { MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, \ - { MP_ROM_QSTR(MP_QSTR_pyb), MP_ROM_PTR(&pyb_module) }, \ + MACHINE_BUILTIN_MODULE \ + MACHINE_BUILTIN_MODULE_CONSTANTS \ + PYB_BUILTIN_MODULE \ STM_BUILTIN_MODULE \ #define MP_STATE_PORT MP_STATE_VM @@ -334,7 +376,7 @@ struct _mp_bluetooth_btstack_root_pointers_t; struct _pyb_uart_obj_t *pyb_stdio_uart; \ \ /* pointers to all UART objects (if they have been created) */ \ - struct _pyb_uart_obj_t *pyb_uart_obj_all[MICROPY_HW_MAX_UART]; \ + struct _pyb_uart_obj_t *pyb_uart_obj_all[MICROPY_HW_MAX_UART + MICROPY_HW_MAX_LPUART]; \ \ /* pointers to all CAN objects (if they have been created) */ \ struct _pyb_can_obj_t *pyb_can_obj_all[MICROPY_HW_MAX_CAN]; \ @@ -362,8 +404,6 @@ typedef unsigned int mp_uint_t; // must be pointer size typedef long mp_off_t; -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - // We have inlined IRQ functions for efficiency (they are generally // 1 machine instruction). // @@ -422,12 +462,22 @@ 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) // We need to provide a declaration/definition of alloca() #include + +// Needed for MICROPY_PY_URANDOM_SEED_INIT_FUNC. +uint32_t rng_get(void); diff --git a/ports/stm32/mpconfigport.mk b/ports/stm32/mpconfigport.mk index c6b3ddc74..98ae93d20 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 diff --git a/ports/stm32/mpnimbleport.c b/ports/stm32/mpnimbleport.c index 1d7c09513..0ba76fb27 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 585f81e8b..aa8f90e3e 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/pin_defs_stm32.h b/ports/stm32/pin_defs_stm32.h index 33a179080..9285c190a 100644 --- a/ports/stm32/pin_defs_stm32.h +++ b/ports/stm32/pin_defs_stm32.h @@ -47,6 +47,7 @@ enum { AF_FN_I2C, AF_FN_USART, AF_FN_UART = AF_FN_USART, + AF_FN_LPUART, AF_FN_SPI, AF_FN_I2S, AF_FN_SDMMC, @@ -77,6 +78,10 @@ enum { AF_PIN_TYPE_UART_RX = AF_PIN_TYPE_USART_RX, AF_PIN_TYPE_UART_CTS = AF_PIN_TYPE_USART_CTS, AF_PIN_TYPE_UART_RTS = AF_PIN_TYPE_USART_RTS, + AF_PIN_TYPE_LPUART_TX = AF_PIN_TYPE_USART_TX, + AF_PIN_TYPE_LPUART_RX = AF_PIN_TYPE_USART_RX, + AF_PIN_TYPE_LPUART_CTS = AF_PIN_TYPE_USART_CTS, + AF_PIN_TYPE_LPUART_RTS = AF_PIN_TYPE_USART_RTS, AF_PIN_TYPE_SPI_MOSI = 0, AF_PIN_TYPE_SPI_MISO, diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index 946185fba..bf8be647f 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -32,8 +32,18 @@ #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 +// 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 @@ -492,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 @@ -506,6 +523,22 @@ void powerctrl_enter_stop_mode(void) { HAL_PWREx_EnableFlashPowerDown(); #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(); + + // 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 @@ -528,6 +561,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); @@ -573,9 +617,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 @@ -592,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); } @@ -622,6 +701,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; @@ -634,7 +717,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; @@ -647,6 +731,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" diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 224b8f28b..3e55069ab 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)) { @@ -203,6 +263,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/pyb_i2c.c b/ports/stm32/pyb_i2c.c index 4f8f7a0e3..ee8b498a1 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) { diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index dc23bbe0b..55fc229dd 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -30,6 +30,8 @@ #include "py/mperrno.h" #include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/modbluetooth.h" #include "rtc.h" #include "rfcore.h" @@ -37,6 +39,21 @@ #include "stm32wbxx_ll_ipcc.h" +#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 BLE events." +#endif + +#endif + #define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) // Define to 1 to print traces of HCI packets @@ -51,10 +68,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)) @@ -64,11 +83,20 @@ #define HCI_KIND_VENDOR_RESPONSE (0x11) #define HCI_KIND_VENDOR_EVENT (0x12) -#define HCI_EVENT_COMMAND_COMPLETE (0x0E) // +#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) +// AN5185 +#define MAGIC_FUS_ACTIVE 0xA94656B9 +// 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; @@ -184,9 +212,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 @@ -267,10 +292,20 @@ 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) { +// 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; - size_t len = 0; - 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: { info = "HCI_ACL"; @@ -284,6 +319,11 @@ STATIC void 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) { @@ -292,10 +332,12 @@ STATIC void 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) { @@ -305,15 +347,21 @@ STATIC void 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; } @@ -334,6 +382,7 @@ STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { } default: info = "HCI_UNKNOWN"; + len = 0; break; } @@ -347,13 +396,15 @@ STATIC void 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"); #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) { @@ -380,77 +431,87 @@ 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); - } -} -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; + if (ch == IPCC_CH_BLE) { + // Renable IRQs for BLE now that we've cleared the flag. + LL_C1_IPCC_EnableReceiveChannel(IPCC, IPCC_CH_BLE); + } } } -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; + n->next = NULL; + n->prev = NULL; cmd[8] = hdr; cmd[9] = opcode; cmd[10] = opcode >> 8; 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, 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; } } - // 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, 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, timeout_ms); } 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; } } - // C2 set IPCC flag. - tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); + // 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, 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) { + // 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(); - } /******************************************************************************/ @@ -487,43 +548,46 @@ static const struct { uint16_t AttMtu; uint16_t SlaveSca; uint8_t MasterSca; - uint8_t LsSource; // 0=LSE 1=internal RO + uint8_t LsSource; uint32_t MaxConnEventLength; uint16_t HsStartupTime; uint8_t ViterbiEnable; - uint8_t LlOnly; // 0=LL+Host, 1=LL only + uint8_t LlOnly; uint8_t HwVersion; } ble_init_params = { - 0, - 0, - 0, // NumAttrRecord - 0, // NumAttrServ - 0, // AttrValueArrSize - 1, // NumOfLinks - 1, // ExtendedPacketLengthEnable - 0, // PrWriteListSize - 0x79, // MblockCount - 0, // AttMtu - 0, // SlaveSca - 0, // MasterSca - 1, // LsSource - 0xffffffff, // MaxConnEventLength - 0x148, // HsStartupTime - 0, // ViterbiEnable - 1, // LlOnly + 0, // pBleBufferAddress + 0, // BleBufferSize + MICROPY_HW_RFCORE_BLE_NUM_GATT_ATTRIBUTES, + MICROPY_HW_RFCORE_BLE_NUM_GATT_SERVICES, + MICROPY_HW_RFCORE_BLE_ATT_VALUE_ARRAY_SIZE, + MICROPY_HW_RFCORE_BLE_NUM_LINK, + MICROPY_HW_RFCORE_BLE_DATA_LENGTH_EXTENSION, + MICROPY_HW_RFCORE_BLE_PREPARE_WRITE_LIST_SIZE, + MICROPY_HW_RFCORE_BLE_MBLOCK_COUNT, + MICROPY_HW_RFCORE_BLE_MAX_ATT_MTU, + MICROPY_HW_RFCORE_BLE_SLAVE_SCA, + MICROPY_HW_RFCORE_BLE_MASTER_SCA, + MICROPY_HW_RFCORE_BLE_LSE_SOURCE, + MICROPY_HW_RFCORE_BLE_MAX_CONN_EVENT_LENGTH, + MICROPY_HW_RFCORE_BLE_HSE_STARTUP_TIME, + MICROPY_HW_RFCORE_BLE_VITERBI_MODE, + MICROPY_HW_RFCORE_BLE_LL_ONLY, 0, // HwVersion }; 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 - 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); + // 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) { @@ -545,13 +609,27 @@ 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; + } + #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}. + 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. @@ -560,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_check_msg_ble(&ipcc_mem_ble_evt_queue, &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) { @@ -573,17 +651,29 @@ 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); } +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); IRQ_EXIT(IPCC_C1_TX_IRQn); @@ -592,17 +682,73 @@ void IPCC_C1_TX_IRQHandler(void) { void IPCC_C1_RX_IRQHandler(void) { IRQ_ENTER(IPCC_C1_RX_IRQn); + DEBUG_printf("IPCC_C1_RX_IRQHandler\n"); + if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { - had_ble_irq = true; + // 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. + extern void mp_bluetooth_hci_systick(uint32_t ticks_ms); + mp_bluetooth_hci_systick(0); + #endif + } - LL_C1_IPCC_ClearFlag_CHx(IPCC, IPCC_CH_BLE); + 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); +} - // Schedule PENDSV to process incoming HCI payload. - extern void mp_bluetooth_hci_poll_wrapper(uint32_t ticks_ms); - mp_bluetooth_hci_poll_wrapper(0); +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); - IRQ_EXIT(IPCC_C1_RX_IRQn); +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(args[0]); + mp_int_t ocf = mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo = {0}; + 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_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 fbe111e1e..fe29ac612 100644 --- a/ports/stm32/rfcore.h +++ b/ports/stm32/rfcore.h @@ -35,4 +35,11 @@ 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); + #endif // MICROPY_INCLUDED_STM32_RFCORE_H diff --git a/ports/stm32/rng.c b/ports/stm32/rng.c index b23941998..eea02f726 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; diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index 18ecf7750..02b0f2dbd 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: @@ -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; } } diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index 9d7772230..b255ee82c 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. @@ -875,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/sdio.c b/ports/stm32/sdio.c index 79cc9c9a3..87f550e3a 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 diff --git a/ports/stm32/sdram.c b/ports/stm32/sdram.c index 0aae74e11..e8892b574 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 9b4b4fb83..773a30802 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__ diff --git a/ports/stm32/spi.c b/ports/stm32/spi.c index 5f9b1c1a2..07374d7a3 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/spi.h b/ports/stm32/spi.h index 885fb0bd6..17f1bf6c4 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/stm32/spibdev.c b/ports/stm32/spibdev.c index 05c8819a7..5090b43b1 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 diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 8e96da177..dc96e2dd6 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -742,11 +742,13 @@ void USART1_IRQHandler(void) { IRQ_EXIT(USART1_IRQn); } +#if defined(USART2) void USART2_IRQHandler(void) { IRQ_ENTER(USART2_IRQn); uart_irq_handler(2); IRQ_EXIT(USART2_IRQn); } +#endif #if defined(STM32F0) @@ -772,29 +774,37 @@ void USART4_5_IRQHandler(void) { #else +#if defined(USART3) void USART3_IRQHandler(void) { IRQ_ENTER(USART3_IRQn); uart_irq_handler(3); IRQ_EXIT(USART3_IRQn); } +#endif +#if defined(UART4) void UART4_IRQHandler(void) { IRQ_ENTER(UART4_IRQn); uart_irq_handler(4); IRQ_EXIT(UART4_IRQn); } +#endif +#if defined(UART5) void UART5_IRQHandler(void) { IRQ_ENTER(UART5_IRQn); uart_irq_handler(5); IRQ_EXIT(UART5_IRQn); } +#endif +#if defined(USART6) void USART6_IRQHandler(void) { IRQ_ENTER(USART6_IRQn); uart_irq_handler(6); IRQ_EXIT(USART6_IRQn); } +#endif #if defined(UART7) void UART7_IRQHandler(void) { @@ -830,6 +840,14 @@ void UART10_IRQHandler(void) { #endif +#if defined(LPUART1) +void LPUART1_IRQHandler(void) { + IRQ_ENTER(LPUART1_IRQn); + uart_irq_handler(PYB_LPUART_1); + IRQ_EXIT(LPUART1_IRQn); +} +#endif + #if MICROPY_PY_PYB_LEGACY #if defined(MICROPY_HW_I2C1_SCL) diff --git a/ports/stm32/storage.c b/ports/stm32/storage.c index 0fefcbab9..6581860ff 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 @@ -453,7 +460,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 diff --git a/ports/stm32/storage.h b/ports/stm32/storage.h index 490fc4a09..73058aad6 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); diff --git a/ports/stm32/system_stm32.c b/ports/stm32/system_stm32.c index 00b721386..2160e0ac9 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 diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 9b8c14c0d..a34d2984d 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 diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index b14a18c6d..d40a883c5 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -201,6 +201,11 @@ bool uart_exists(int uart_id) { return true; #endif + #if defined(MICROPY_HW_LPUART1_TX) && defined(MICROPY_HW_LPUART1_RX) + case PYB_LPUART_1: + return true; + #endif + default: return false; } @@ -211,6 +216,7 @@ bool uart_init(pyb_uart_obj_t *uart_obj, uint32_t baudrate, uint32_t bits, uint32_t parity, uint32_t stop, uint32_t flow) { USART_TypeDef *UARTx; IRQn_Type irqn; + uint8_t uart_fn = AF_FN_UART; int uart_unit; const pin_obj_t *pins[4] = {0}; @@ -406,6 +412,28 @@ bool uart_init(pyb_uart_obj_t *uart_obj, break; #endif + #if defined(MICROPY_HW_LPUART1_TX) && defined(MICROPY_HW_LPUART1_RX) + case PYB_LPUART_1: + uart_fn = AF_FN_LPUART; + uart_unit = 1; + UARTx = LPUART1; + irqn = LPUART1_IRQn; + pins[0] = MICROPY_HW_LPUART1_TX; + pins[1] = MICROPY_HW_LPUART1_RX; + #if defined(MICROPY_HW_LPUART1_RTS) + if (flow & UART_HWCONTROL_RTS) { + pins[2] = MICROPY_HW_LPUART1_RTS; + } + #endif + #if defined(MICROPY_HW_LPUART1_CTS) + if (flow & UART_HWCONTROL_CTS) { + pins[3] = MICROPY_HW_LPUART1_CTS; + } + #endif + __HAL_RCC_LPUART1_CLK_ENABLE(); + break; + #endif + default: // UART does not exist or is not configured for this board return false; @@ -416,7 +444,7 @@ bool uart_init(pyb_uart_obj_t *uart_obj, for (uint i = 0; i < 4; i++) { if (pins[i] != NULL) { - bool ret = mp_hal_pin_config_alt(pins[i], mode, pull, AF_FN_UART, uart_unit); + bool ret = mp_hal_pin_config_alt(pins[i], mode, pull, uart_fn, uart_unit); if (!ret) { return false; } @@ -596,6 +624,13 @@ void uart_deinit(pyb_uart_obj_t *self) { __HAL_RCC_UART10_RELEASE_RESET(); __HAL_RCC_UART10_CLK_DISABLE(); #endif + #if defined(LPUART1) + } else if (self->uart_id == PYB_LPUART_1) { + HAL_NVIC_DisableIRQ(LPUART1_IRQn); + __HAL_RCC_LPUART1_FORCE_RESET(); + __HAL_RCC_LPUART1_RELEASE_RESET(); + __HAL_RCC_LPUART1_CLK_DISABLE(); + #endif } } @@ -603,7 +638,7 @@ void uart_attach_to_repl(pyb_uart_obj_t *self, bool attached) { self->attached_to_repl = attached; } -uint32_t uart_get_baudrate(pyb_uart_obj_t *self) { +uint32_t uart_get_source_freq(pyb_uart_obj_t *self) { uint32_t uart_clk = 0; #if defined(STM32F0) @@ -672,10 +707,28 @@ uint32_t uart_get_baudrate(pyb_uart_obj_t *self) { } #endif + return uart_clk; +} + +uint32_t uart_get_baudrate(pyb_uart_obj_t *self) { // This formula assumes UART_OVERSAMPLING_16 - uint32_t baudrate = uart_clk / self->uartx->BRR; + uint32_t source_freq = uart_get_source_freq(self); + #if defined(LPUART1) + if (self->uart_id == PYB_LPUART_1) { + return source_freq / (self->uartx->BRR >> 8); + } else + #endif + { + return source_freq / self->uartx->BRR; + } +} - return baudrate; +void uart_set_baudrate(pyb_uart_obj_t *self, uint32_t baudrate) { + LL_USART_SetBaudRate(self->uartx, uart_get_source_freq(self), + #if defined(STM32H7) || defined(STM32WB) + LL_USART_PRESCALER_DIV1, + #endif + LL_USART_OVERSAMPLING_16, baudrate); } mp_uint_t uart_rx_any(pyb_uart_obj_t *self) { diff --git a/ports/stm32/uart.h b/ports/stm32/uart.h index 9a38db593..0490a617f 100644 --- a/ports/stm32/uart.h +++ b/ports/stm32/uart.h @@ -40,6 +40,9 @@ typedef enum { PYB_UART_8 = 8, PYB_UART_9 = 9, PYB_UART_10 = 10, + #ifdef LPUART1 + PYB_LPUART_1 = MICROPY_HW_MAX_UART + 1, + #endif } pyb_uart_t; #define CHAR_WIDTH_8BIT (0) @@ -86,6 +89,8 @@ void uart_irq_handler(mp_uint_t uart_id); void uart_attach_to_repl(pyb_uart_obj_t *self, bool attached); uint32_t uart_get_baudrate(pyb_uart_obj_t *self); +void uart_set_baudrate(pyb_uart_obj_t *self, uint32_t baudrate); + mp_uint_t uart_rx_any(pyb_uart_obj_t *uart_obj); bool uart_rx_wait(pyb_uart_obj_t *self, uint32_t timeout); int uart_rx_char(pyb_uart_obj_t *uart_obj); diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index 968b2999c..5003bb27c 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 }; diff --git a/ports/stm32/usb.h b/ports/stm32/usb.h index f69af86e5..295038ebd 100644 --- a/ports/stm32/usb.h +++ b/ports/stm32/usb.h @@ -30,6 +30,7 @@ #define PYB_USB_FLAG_USB_MODE_CALLED (0x0002) +#ifndef USBD_VID // Windows needs a different PID to distinguish different device configurations #define USBD_VID (0xf055) #define USBD_PID_CDC_MSC (0x9800) @@ -43,6 +44,7 @@ #define USBD_PID_CDC_MSC_HID (0x9808) #define USBD_PID_CDC2_MSC_HID (0x9809) #define USBD_PID_CDC3_MSC_HID (0x980a) +#endif typedef enum { PYB_USB_STORAGE_MEDIUM_NONE = 0, diff --git a/ports/stm32/usbd_cdc_interface.c b/ports/stm32/usbd_cdc_interface.c index 7a0912852..a465f608a 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); @@ -378,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; @@ -386,7 +394,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); } diff --git a/ports/teensy/Makefile b/ports/teensy/Makefile index f2623eabd..d3978718e 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 f4384a885..6671b3abd 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 24269c3b9..3acedcf02 100644 --- a/ports/teensy/mpconfigport.h +++ b/ports/teensy/mpconfigport.h @@ -62,8 +62,6 @@ typedef int32_t mp_int_t; // must be pointer size typedef unsigned int mp_uint_t; // must be pointer size typedef long mp_off_t; -#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) - // We have inlined IRQ functions for efficiency (they are generally // 1 machine instruction). // @@ -72,10 +70,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 +86,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; } diff --git a/ports/unix/Makefile b/ports/unix/Makefile index ff5f35502..6a936a242 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 @@ -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) @@ -184,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 @@ -220,7 +219,7 @@ SRC_MOD += modjni.c endif # source files -SRC_C = \ +SRC_C += \ main.c \ gccollect.c \ unix_mphal.c \ @@ -232,7 +231,6 @@ SRC_C = \ modtime.c \ moduselect.c \ alloc.c \ - coverage.c \ fatfs_port.c \ mpbthciport.c \ mpbtstackport_common.c \ @@ -248,13 +246,17 @@ LIB_SRC_C += $(addprefix lib/,\ utils/gchelper_generic.c \ ) +SRC_CXX += \ + $(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 +274,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/coverage.c b/ports/unix/coverage.c index 7169c7793..ef66c4fb5 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/ports/unix/coveragecpp.cpp b/ports/unix/coveragecpp.cpp new file mode 100644 index 000000000..ea7418e1d --- /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 0fe492a55..07db8d22c 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -387,7 +387,7 @@ STATIC void pre_process_options(int argc, char **argv) { goto invalid_arg; } if (word_adjust) { - heap_size = heap_size * BYTES_PER_WORD / 4; + heap_size = heap_size * MP_BYTES_PER_OBJ_WORD / 4; } // If requested size too small, we'll crash anyway if (heap_size < 700) { @@ -446,7 +446,7 @@ MP_NOINLINE int main_(int argc, char **argv) { signal(SIGPIPE, SIG_IGN); #endif - mp_stack_set_limit(40000 * (BYTES_PER_WORD / 4)); + mp_stack_set_limit(40000 * (sizeof(void *) / 4)); pre_process_options(argc, argv); @@ -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_store_global(QSTR_FROM_STR_STATIC("extra_coverage"), MP_OBJ_FROM_PTR(&extra_coverage_obj)); + MP_DECLARE_CONST_FUN_OBJ_0(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 @@ -544,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/ports/unix/modmachine.c b/ports/unix/modmachine.c index a6eb8c01e..5b462a3b1 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/ports/unix/modtime.c b/ports/unix/modtime.c index 5343e479c..91c0a1941 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 @@ -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/ports/unix/moduselect.c b/ports/unix/moduselect.c index 6a0ee79aa..a9390a146 100644 --- a/ports/unix/moduselect.c +++ b/ports/unix/moduselect.c @@ -29,6 +29,10 @@ #if MICROPY_PY_USELECT_POSIX +#if MICROPY_PY_USELECT +#error "Can't have both MICROPY_PY_USELECT and MICROPY_PY_USELECT_POSIX." +#endif + #include #include #include diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index 316a8831f..14afbebcd 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/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c index 621e661f9..ec40db65b 100644 --- a/ports/unix/mpbtstackport_common.c +++ b/ports/unix/mpbtstackport_common.c @@ -57,6 +57,11 @@ bool mp_bluetooth_hci_poll(void) { return false; } +bool mp_bluetooth_hci_active(void) { + return mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_OFF + && mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT; +} + // The IRQ functionality in btstack_run_loop_embedded.c is not used, so the // following three functions are empty. diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index c74d2fd84..d838f42b3 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) @@ -362,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(); diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index 95ad63221..03da1e3d8 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -30,7 +30,7 @@ #define CHAR_CTRL_C (3) #endif -void mp_hal_set_interrupt_char(char c); +void mp_hal_set_interrupt_char(int c); #define mp_hal_stdio_poll unused // this is not implemented, nor needed void mp_hal_stdio_mode_raw(void); @@ -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/mpnimbleport.c b/ports/unix/mpnimbleport.c index 896191009..29f558f74 100644 --- a/ports/unix/mpnimbleport.c +++ b/ports/unix/mpnimbleport.c @@ -47,38 +47,28 @@ bool mp_bluetooth_hci_poll(void) { } if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + // Run any timers. + mp_bluetooth_nimble_os_callout_process(); - // Pretend like we're running in IRQ context (i.e. other things can't be running at the same time). - mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); - - // Ask NimBLE to process UART data. - mp_bluetooth_nimble_hci_uart_process(); + // Process incoming UART data, and run events as they are generated. + mp_bluetooth_nimble_hci_uart_process(true); - // Run pending background operations and events, but only after HCI sync. - mp_bluetooth_nimble_os_callout_process(); + // Run any remaining events (e.g. if there was no UART data). mp_bluetooth_nimble_os_eventq_run_all(); - - MICROPY_END_ATOMIC_SECTION(atomic_state); } return true; } -// Extra port-specific helpers. -void mp_bluetooth_nimble_hci_uart_wfi(void) { - // DEBUG_printf("mp_bluetooth_nimble_hci_uart_wfi\n"); - // TODO: this should do a select() on the uart_fd. -} - -uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { - // DEBUG_printf("mp_bluetooth_nimble_hci_uart_enter_critical\n"); - MICROPY_PY_BLUETOOTH_ENTER - return atomic_state; // Always 0xffffffff +bool mp_bluetooth_hci_active(void) { + return mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; } -void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { - MICROPY_PY_BLUETOOTH_EXIT - // DEBUG_printf("mp_bluetooth_nimble_hci_uart_exit_critical\n"); +// Extra port-specific helpers. +void mp_bluetooth_nimble_hci_uart_wfi(void) { + // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. + // Do not need to run events here, only processing incoming HCI data. + mp_bluetooth_nimble_hci_uart_process(false); } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index de0f5923b..cbc4901f6 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -206,7 +206,7 @@ void mp_thread_start(void) { void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { // default stack size is 8k machine-words if (*stack_size == 0) { - *stack_size = 8192 * BYTES_PER_WORD; + *stack_size = 8192 * sizeof(void *); } // minimum stack size is set by pthreads diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index f43067f64..28a4ca2c4 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -216,6 +216,21 @@ 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; +} + +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/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index 3de529432..942117608 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/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index f11d0b0d2..ef81975d9 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -7,13 +7,18 @@ 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 MICROPY_VFS_LFS1 = 1 MICROPY_VFS_LFS2 = 1 + +SRC_C += coverage.c +SRC_CXX += coveragecpp.cpp diff --git a/ports/wapy-unix/Makefile b/ports/wapy-unix/Makefile new file mode 100644 index 000000000..b74f0467d --- /dev/null +++ b/ports/wapy-unix/Makefile @@ -0,0 +1,303 @@ +# Select the variant to build for. +VARIANT ?= standard + +# If the build directory is not given, make it reflect the variant name. +BUILD ?= build-$(VARIANT) + +VARIANT_DIR ?= variants/$(VARIANT) +ifeq ($(wildcard $(VARIANT_DIR)/.),) +$(error Invalid VARIANT specified: $(VARIANT_DIR)) +endif + +include ../../py/mkenv.mk +-include mpconfigport.mk +include $(VARIANT_DIR)/mpconfigvariant.mk + +# use FROZEN_MANIFEST for new projects, others are legacy +FROZEN_MANIFEST ?= manifest.py +FROZEN_DIR = +FROZEN_MPY_DIR = + +# This should be configured by the mpconfigvariant.mk +PROG ?= micropython + +# qstr definitions (must come before including py.mk) +QSTR_DEFS = qstrdefsport.h +QSTR_GLOBAL_DEPENDENCIES = $(VARIANT_DIR)/mpconfigvariant.h + +# OS name, for simple autoconfig +UNAME_S := $(shell uname -s) + +# include py core make definitions +include $(TOP)/py/py.mk + +GIT_SUBMODULES += lib/axtls lib/berkeley-db-1.xx lib/libffi + +INC += -I. +INC += -I$(TOP) +INC += -I$(BUILD) + +# compiler settings +CWARN = -Wall -Werror +CWARN += -Wpointer-arith -Wuninitialized -Wdouble-promotion -Wsign-compare -Wfloat-conversion +CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(CFLAGS_MOD) $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EXTRA) + +# Debugging/Optimization +ifdef DEBUG +COPT ?= -O0 +else +COPT ?= -Os +COPT += -fdata-sections -ffunction-sections +COPT += -DNDEBUG +endif + +# 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 + +ifndef DEBUG +# _FORTIFY_SOURCE is a feature in gcc/glibc which is intended to provide extra +# security for detecting buffer overflows. Some distros (Ubuntu at the very least) +# have it enabled by default. +# +# gcc already optimizes some printf calls to call puts and/or putchar. When +# _FORTIFY_SOURCE is enabled and compiling with -O1 or greater, then some +# printf calls will also be optimized to call __printf_chk (in glibc). Any +# printfs which get redirected to __printf_chk are then no longer synchronized +# with printfs that go through mp_printf. +# +# In MicroPython, we don't want to use the runtime library's printf but rather +# go through mp_printf, so that stdout is properly tied into streams, etc. +# This means that we either need to turn off _FORTIFY_SOURCE or provide our +# own implementation of __printf_chk. We've chosen to turn off _FORTIFY_SOURCE. +# It should also be noted that the use of printf in MicroPython is typically +# quite limited anyways (primarily for debug and some error reporting, etc +# in the unix version). +# +# Information about _FORTIFY_SOURCE seems to be rather scarce. The best I could +# find was this: https://securityblog.redhat.com/2014/03/26/fortify-and-you/ +# Original patchset was introduced by +# https://gcc.gnu.org/ml/gcc-patches/2004-09/msg02055.html . +# +# Turning off _FORTIFY_SOURCE is only required when compiling with -O1 or greater +CFLAGS += -U _FORTIFY_SOURCE +endif + +# On OSX, 'gcc' is a symlink to clang unless a real gcc is installed. +# The unix port of MicroPython on OSX must be compiled with clang, +# while cross-compile ports require gcc, so we test here for OSX and +# if necessary override the value of 'CC' set in py/mkenv.mk +ifeq ($(UNAME_S),Darwin) +ifeq ($(MICROPY_FORCE_32BIT),1) +CC = clang -m32 +else +CC = clang +endif +# Use clang syntax for map file +LDFLAGS_ARCH = -Wl,-map,$@.map -Wl,-dead_strip +else +# Use gcc syntax for map file +LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections +endif +LDFLAGS += $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) + +# Flags to link with pthread library +LIBPTHREAD = -lpthread + +ifeq ($(MICROPY_FORCE_32BIT),1) +# Note: you may need to install i386 versions of dependency packages, +# starting with linux-libc-dev:i386 +ifeq ($(MICROPY_PY_FFI),1) +ifeq ($(UNAME_S),Linux) +CFLAGS_MOD += -I/usr/include/i686-linux-gnu +endif +endif +endif + +ifeq ($(MICROPY_USE_READLINE),1) +INC += -I$(TOP)/lib/mp-readline +CFLAGS_MOD += -DMICROPY_USE_READLINE=1 +LIB_SRC_C_EXTRA += mp-readline/readline.c +endif +ifeq ($(MICROPY_PY_TERMIOS),1) +CFLAGS_MOD += -DMICROPY_PY_TERMIOS=1 +SRC_MOD += modtermios.c +endif +ifeq ($(MICROPY_PY_SOCKET),1) +CFLAGS_MOD += -DMICROPY_PY_SOCKET=1 +SRC_MOD += modusocket.c +endif +ifeq ($(MICROPY_PY_THREAD),1) +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. +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) + +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 + +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) + +ifeq ($(MICROPY_STANDALONE),1) +LIBFFI_CFLAGS_MOD := -I$(shell ls -1d $(BUILD)/lib/libffi/out/lib/libffi-*/include) + ifeq ($(MICROPY_FORCE_32BIT),1) + LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib32/libffi.a + else + LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib/libffi.a + endif +else +LIBFFI_CFLAGS_MOD := $(shell pkg-config --cflags libffi) +LIBFFI_LDFLAGS_MOD := $(shell pkg-config --libs libffi) +endif + +ifeq ($(UNAME_S),Linux) +LIBFFI_LDFLAGS_MOD += -ldl +endif + +CFLAGS_MOD += $(LIBFFI_CFLAGS_MOD) -DMICROPY_PY_FFI=1 +LDFLAGS_MOD += $(LIBFFI_LDFLAGS_MOD) +SRC_MOD += modffi.c +endif + +ifeq ($(MICROPY_PY_JNI),1) +# Path for 64-bit OpenJDK, should be adjusted for other JDKs +CFLAGS_MOD += -I/usr/lib/jvm/java-7-openjdk-amd64/include -DMICROPY_PY_JNI=1 +SRC_MOD += modjni.c +endif + +include ../wapy/wapy.mk + +# source files + + +SRC_C = \ + main.c \ + file.c \ + gccollect.c \ + unix_mphal.c \ + mpthreadport.c \ + input.c \ + modmachine.c \ + modos.c \ + moduos_vfs.c \ + modtime.c \ + moduselect.c \ + alloc.c \ + coverage.c \ + fatfs_port.c \ + btstack_usb.c \ + $(SRC_MOD) \ + $(wildcard $(VARIANT_DIR)/*.c) + +LIB_SRC_C += $(addprefix lib/,\ + $(LIB_SRC_C_EXTRA) \ + timeutils/timeutils.c \ + utils/gchelper_generic.c \ + ) + +OBJ = $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.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) +# Append any auto-generated sources that are needed by sources listed in +# SRC_QSTR +SRC_QSTR_AUTO_DEPS += + +ifneq ($(FROZEN_MANIFEST)$(FROZEN_MPY_DIR),) +# To use frozen code create a manifest.py file with a description of files to +# freeze, then invoke make with FROZEN_MANIFEST=manifest.py (be sure to build from scratch). +CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool +CFLAGS += -DMICROPY_MODULE_FROZEN_MPY +CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs +MPY_CROSS_FLAGS += -mcache-lookup-bc +endif + +ifneq ($(FROZEN_MANIFEST)$(FROZEN_DIR),) +CFLAGS += -DMICROPY_MODULE_FROZEN_STR +endif + +ifeq ($(MICROPY_FORCE_32BIT),1) +RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-mcache-lookup-bc -march=x86' +else +RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-mcache-lookup-bc' +endif + +include $(TOP)/py/mkrules.mk + +.PHONY: test test_full + +test: $(PROG) $(TOP)/tests/run-tests + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests + +test_full: $(PROG) $(TOP)/tests/run-tests + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests -d thread + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests --emit native + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) -d basics float micropython + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(PROG) ./run-tests --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython + cat $(TOP)/tests/basics/0prelim.py | ./$(PROG) | grep -q 'abc' + +test_gcov: test_full + gcov -o $(BUILD)/py $(TOP)/py/*.c + gcov -o $(BUILD)/extmod $(TOP)/extmod/*.c + +# Value of configure's --host= option (required for cross-compilation). +# Deduce it from CROSS_COMPILE by default, but can be overridden. +ifneq ($(CROSS_COMPILE),) +CROSS_COMPILE_HOST = --host=$(patsubst %-,%,$(CROSS_COMPILE)) +else +CROSS_COMPILE_HOST = +endif + +deplibs: libffi axtls + +libffi: $(BUILD)/lib/libffi/include/ffi.h + +$(TOP)/lib/libffi/configure: $(TOP)/lib/libffi/autogen.sh + cd $(TOP)/lib/libffi; ./autogen.sh + +# install-exec-recursive & install-data-am targets are used to avoid building +# docs and depending on makeinfo +$(BUILD)/lib/libffi/include/ffi.h: $(TOP)/lib/libffi/configure + mkdir -p $(BUILD)/lib/libffi; cd $(BUILD)/lib/libffi; \ + $(abspath $(TOP))/lib/libffi/configure $(CROSS_COMPILE_HOST) --prefix=$$PWD/out --disable-structs CC="$(CC)" CXX="$(CXX)" LD="$(LD)" CFLAGS="-Os -fomit-frame-pointer -fstrict-aliasing -ffast-math -fno-exceptions"; \ + $(MAKE) install-exec-recursive; $(MAKE) -C include install-data-am + +axtls: $(TOP)/lib/axtls/README + +$(TOP)/lib/axtls/README: + @echo "You cloned without --recursive, fetching submodules for you." + (cd $(TOP); git submodule update --init --recursive) + +PREFIX = /usr/local +BINDIR = $(DESTDIR)$(PREFIX)/bin + +install: $(PROG) + install -d $(BINDIR) + install $(PROG) $(BINDIR)/$(PROG) + +uninstall: + -rm $(BINDIR)/$(PROG) diff --git a/ports/wapy-unix/alloc.c b/ports/wapy-unix/alloc.c new file mode 100644 index 000000000..7fe7b4dba --- /dev/null +++ b/ports/wapy-unix/alloc.c @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Fabian Vogt + * + * 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/mpstate.h" +#include "py/gc.h" + +#if MICROPY_EMIT_NATIVE || (MICROPY_PY_FFI && MICROPY_FORCE_PLAT_ALLOC_EXEC) + +#if defined(__OpenBSD__) || defined(__MACH__) +#define MAP_ANONYMOUS MAP_ANON +#endif + +// The memory allocated here is not on the GC heap (and it may contain pointers +// that need to be GC'd) so we must somehow trace this memory. We do it by +// keeping a linked list of all mmap'd regions, and tracing them explicitly. + +typedef struct _mmap_region_t { + void *ptr; + size_t len; + struct _mmap_region_t *next; +} mmap_region_t; + +void mp_unix_alloc_exec(size_t min_size, void **ptr, size_t *size) { + // size needs to be a multiple of the page size + *size = (min_size + 0xfff) & (~0xfff); + *ptr = mmap(NULL, *size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (*ptr == MAP_FAILED) { + *ptr = NULL; + } + + // add new link to the list of mmap'd regions + mmap_region_t *rg = m_new_obj(mmap_region_t); + rg->ptr = *ptr; + rg->len = min_size; + rg->next = MP_STATE_VM(mmap_region_head); + MP_STATE_VM(mmap_region_head) = rg; +} + +void mp_unix_free_exec(void *ptr, size_t size) { + munmap(ptr, size); + + // unlink the mmap'd region from the list + for (mmap_region_t **rg = (mmap_region_t **)&MP_STATE_VM(mmap_region_head); *rg != NULL; *rg = (*rg)->next) { + if ((*rg)->ptr == ptr) { + mmap_region_t *next = (*rg)->next; + m_del_obj(mmap_region_t, *rg); + *rg = next; + return; + } + } +} + +void mp_unix_mark_exec(void) { + for (mmap_region_t *rg = MP_STATE_VM(mmap_region_head); rg != NULL; rg = rg->next) { + gc_collect_root(rg->ptr, rg->len / sizeof(mp_uint_t)); + } +} + +#if MICROPY_FORCE_PLAT_ALLOC_EXEC +// Provide implementation of libffi ffi_closure_* functions in terms +// of the functions above. On a normal Linux system, this save a lot +// of code size. +void *ffi_closure_alloc(size_t size, void **code); +void ffi_closure_free(void *ptr); + +void *ffi_closure_alloc(size_t size, void **code) { + size_t dummy; + mp_unix_alloc_exec(size, code, &dummy); + return *code; +} + +void ffi_closure_free(void *ptr) { + (void)ptr; + // TODO +} +#endif + +#endif // MICROPY_EMIT_NATIVE || (MICROPY_PY_FFI && MICROPY_FORCE_PLAT_ALLOC_EXEC) diff --git a/ports/wapy-unix/aosp_mphal.c b/ports/wapy-unix/aosp_mphal.c new file mode 100644 index 000000000..ae23bb6f1 --- /dev/null +++ b/ports/wapy-unix/aosp_mphal.c @@ -0,0 +1,148 @@ +#include +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#include "../wapy/upython.h" + +#include + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + +struct timespec ts; +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + +mp_uint_t mp_hal_ticks_ms(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us -1000; + return (mp_uint_t)( (now_us - epoch_us) / 1000 ) ; +#else + return (mp_uint_t)(now_us / 1000); +#endif + +} + +mp_uint_t mp_hal_ticks_us(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); +#else + return (mp_uint_t)now_us; +#endif + +} + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + fprintf(stderr,"mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + if (c>127) + fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + if (c>127) + fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick + +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + for(int i=0;i +#include + +#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" + +#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(); + } + + // TODO: allow setting USB device path via cmdline/env var. + + // 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; + +void mp_bluetooth_btstack_port_deinit(void) { + hci_power_control(HCI_POWER_OFF); + + // Wait for the poll loop to terminate when the state is set to OFF. + pthread_join(bstack_thread_id, NULL); + have_addr = false; +} + +STATIC void *btstack_thread(void *arg) { + (void)arg; + hci_power_control(HCI_POWER_ON); + + // modbluetooth_btstack.c will have set the state to STARTING before + // calling mp_bluetooth_btstack_port_start. + // This loop will terminate when the HCI_POWER_OFF above results + // 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) { + btstack_run_loop_embedded_execute_once(); + + // 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; +} + +void mp_bluetooth_btstack_port_start(void) { + // Create a thread to run the btstack loop. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + 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 diff --git a/ports/wapy-unix/coverage-frzmpy/frzmpy1.py b/ports/wapy-unix/coverage-frzmpy/frzmpy1.py new file mode 100644 index 000000000..8ad0f1573 --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy1.py @@ -0,0 +1 @@ +print('frzmpy1') diff --git a/ports/wapy-unix/coverage-frzmpy/frzmpy2.py b/ports/wapy-unix/coverage-frzmpy/frzmpy2.py new file mode 100644 index 000000000..1ad930db2 --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy2.py @@ -0,0 +1 @@ +raise ZeroDivisionError diff --git a/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg1/__init__.py b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg1/__init__.py new file mode 100644 index 000000000..8c023afeb --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzmpy_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg2/mod.py b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg2/mod.py new file mode 100644 index 000000000..a66b505bf --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzmpy_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzmpy_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/coverage-frzmpy/frzqstr.py b/ports/wapy-unix/coverage-frzmpy/frzqstr.py new file mode 100644 index 000000000..051f2a9c1 --- /dev/null +++ b/ports/wapy-unix/coverage-frzmpy/frzqstr.py @@ -0,0 +1,3 @@ +# Checks for regression on MP_QSTR_NULL +def returns_NULL(): + return "NULL" diff --git a/ports/wapy-unix/coverage-frzstr/frzstr1.py b/ports/wapy-unix/coverage-frzstr/frzstr1.py new file mode 100644 index 000000000..6e88ac38d --- /dev/null +++ b/ports/wapy-unix/coverage-frzstr/frzstr1.py @@ -0,0 +1 @@ +print('frzstr1') diff --git a/ports/wapy-unix/coverage-frzstr/frzstr_pkg1/__init__.py b/ports/wapy-unix/coverage-frzstr/frzstr_pkg1/__init__.py new file mode 100644 index 000000000..1d1df9417 --- /dev/null +++ b/ports/wapy-unix/coverage-frzstr/frzstr_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzstr_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/coverage-frzstr/frzstr_pkg2/mod.py b/ports/wapy-unix/coverage-frzstr/frzstr_pkg2/mod.py new file mode 100644 index 000000000..bafb5978b --- /dev/null +++ b/ports/wapy-unix/coverage-frzstr/frzstr_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzstr_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/coverage.c b/ports/wapy-unix/coverage.c new file mode 100644 index 000000000..e239abe94 --- /dev/null +++ b/ports/wapy-unix/coverage.c @@ -0,0 +1,641 @@ +#include +#include + +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/repl.h" +#include "py/mpz.h" +#include "py/builtin.h" +#include "py/emit.h" +#include "py/formatfloat.h" +#include "py/ringbuf.h" +#include "py/pairheap.h" +#include "py/stream.h" +#include "py/binary.h" +#include "py/bc.h" + +// expected output of this file is found in extra_coverage.py.exp + +#if defined(MICROPY_UNIX_COVERAGE) + +// stream testing object +typedef struct _mp_obj_streamtest_t { + mp_obj_base_t base; + uint8_t *buf; + size_t len; + size_t pos; + int error_code; +} mp_obj_streamtest_t; + +STATIC mp_obj_t stest_set_buf(mp_obj_t o_in, mp_obj_t buf_in) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + o->buf = m_new(uint8_t, bufinfo.len); + memcpy(o->buf, bufinfo.buf, bufinfo.len); + o->len = bufinfo.len; + o->pos = 0; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(stest_set_buf_obj, stest_set_buf); + +STATIC mp_obj_t stest_set_error(mp_obj_t o_in, mp_obj_t err_in) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + o->error_code = mp_obj_get_int(err_in); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(stest_set_error_obj, stest_set_error); + +STATIC mp_uint_t stest_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + if (o->pos < o->len) { + if (size > o->len - o->pos) { + size = o->len - o->pos; + } + memcpy(buf, o->buf + o->pos, size); + o->pos += size; + return size; + } else if (o->error_code == 0) { + return 0; + } else { + *errcode = o->error_code; + return MP_STREAM_ERROR; + } +} + +STATIC mp_uint_t stest_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + (void)buf; + (void)size; + *errcode = o->error_code; + return MP_STREAM_ERROR; +} + +STATIC mp_uint_t stest_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + mp_obj_streamtest_t *o = MP_OBJ_TO_PTR(o_in); + (void)arg; + (void)request; + (void)errcode; + if (o->error_code != 0) { + *errcode = o->error_code; + return MP_STREAM_ERROR; + } + return 0; +} + +STATIC const mp_rom_map_elem_t rawfile_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_set_buf), MP_ROM_PTR(&stest_set_buf_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_error), MP_ROM_PTR(&stest_set_error_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_read1), MP_ROM_PTR(&mp_stream_read1_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write1), MP_ROM_PTR(&mp_stream_write1_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&mp_stream_ioctl_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table); + +STATIC const mp_stream_p_t fileio_stream_p = { + .read = stest_read, + .write = stest_write, + .ioctl = stest_ioctl, +}; + +STATIC const mp_obj_type_t mp_type_stest_fileio = { + { &mp_type_type }, + .protocol = &fileio_stream_p, + .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict, +}; + +// stream read returns non-blocking error +STATIC mp_uint_t stest_read2(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + (void)o_in; + (void)buf; + (void)size; + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; +} + +STATIC const mp_rom_map_elem_t rawfile_locals_dict_table2[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict2, rawfile_locals_dict_table2); + +STATIC const mp_stream_p_t textio_stream_p2 = { + .read = stest_read2, + .write = NULL, + .is_text = true, +}; + +STATIC const mp_obj_type_t mp_type_stest_textio2 = { + { &mp_type_type }, + .protocol = &textio_stream_p2, + .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict2, +}; + +// str/bytes objects without a valid hash +STATIC const mp_obj_str_t str_no_hash_obj = {{&mp_type_str}, 0, 10, (const byte *)"0123456789"}; +STATIC const mp_obj_str_t bytes_no_hash_obj = {{&mp_type_bytes}, 0, 10, (const byte *)"0123456789"}; + +STATIC int pairheap_lt(mp_pairheap_t *a, mp_pairheap_t *b) { + return (uintptr_t)a < (uintptr_t)b; +} + +// ops array contain operations: x>=0 means push(x), x<0 means delete(-x) +STATIC void pairheap_test(size_t nops, int *ops) { + mp_pairheap_t node[8]; + for (size_t i = 0; i < MP_ARRAY_SIZE(node); ++i) { + mp_pairheap_init_node(pairheap_lt, &node[i]); + } + mp_pairheap_t *heap = mp_pairheap_new(pairheap_lt); + printf("create:"); + for (size_t i = 0; i < nops; ++i) { + if (ops[i] >= 0) { + heap = mp_pairheap_push(pairheap_lt, heap, &node[ops[i]]); + } else { + heap = mp_pairheap_delete(pairheap_lt, heap, &node[-ops[i]]); + } + if (mp_pairheap_is_empty(pairheap_lt, heap)) { + mp_printf(&mp_plat_print, " -"); + } else { + mp_printf(&mp_plat_print, " %d", mp_pairheap_peek(pairheap_lt, heap) - &node[0]); + ; + } + } + printf("\npop all:"); + while (!mp_pairheap_is_empty(pairheap_lt, heap)) { + mp_printf(&mp_plat_print, " %d", mp_pairheap_peek(pairheap_lt, heap) - &node[0]); + ; + heap = mp_pairheap_pop(pairheap_lt, heap); + } + printf("\n"); +} + +// function to run extra tests for things that can't be checked by scripts +STATIC mp_obj_t extra_coverage(void) { + // mp_printf (used by ports that don't have a native printf) + { + mp_printf(&mp_plat_print, "# mp_printf\n"); + mp_printf(&mp_plat_print, "%d %+d % d\n", -123, 123, 123); // sign + mp_printf(&mp_plat_print, "%05d\n", -123); // negative number with zero padding + 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, "%.*s\n", -1, "abc"); // negative string precision + mp_printf(&mp_plat_print, "%b %b\n", 0, 1); // bools + #ifndef NDEBUG + mp_printf(&mp_plat_print, "%s\n", NULL); // null string + #else + mp_printf(&mp_plat_print, "(null)\n"); // without debugging mp_printf won't check for null + #endif + mp_printf(&mp_plat_print, "%d\n", 0x80000000); // should print signed + mp_printf(&mp_plat_print, "%u\n", 0x80000000); // should print unsigned + mp_printf(&mp_plat_print, "%x\n", 0x80000000); // should print unsigned + mp_printf(&mp_plat_print, "%X\n", 0x80000000); // should print unsigned + mp_printf(&mp_plat_print, "abc\n%"); // string ends in middle of format specifier + mp_printf(&mp_plat_print, "%%\n"); // literal % character + } + + // GC + { + mp_printf(&mp_plat_print, "# GC\n"); + + // calling gc_free while GC is locked + gc_lock(); + gc_free(NULL); + gc_unlock(); + + // using gc_realloc to resize to 0, which means free the memory + void *p = gc_alloc(4, false); + mp_printf(&mp_plat_print, "%p\n", gc_realloc(p, 0, false)); + + // calling gc_nbytes with a non-heap pointer + mp_printf(&mp_plat_print, "%p\n", gc_nbytes(NULL)); + } + + // vstr + { + mp_printf(&mp_plat_print, "# vstr\n"); + vstr_t *vstr = vstr_new(16); + vstr_hint_size(vstr, 32); + vstr_add_str(vstr, "ts"); + vstr_ins_byte(vstr, 1, 'e'); + vstr_ins_char(vstr, 3, 't'); + vstr_ins_char(vstr, 10, 's'); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_cut_head_bytes(vstr, 2); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_cut_tail_bytes(vstr, 10); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_printf(vstr, "t%cst", 'e'); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + vstr_cut_out_bytes(vstr, 3, 10); + mp_printf(&mp_plat_print, "%.*s\n", (int)vstr->len, vstr->buf); + + VSTR_FIXED(fix, 4); + if (vstr_add_str(&fix, "large")) { + mp_obj_print_exception(&mp_plat_print, MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + } + + fix.len = fix.alloc; + if (vstr_null_terminated_str(&fix) == NULL) { + mp_obj_print_exception(&mp_plat_print, MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + } + } + + // repl autocomplete + { + mp_printf(&mp_plat_print, "# repl\n"); + + const char *str; + size_t len = mp_repl_autocomplete("__n", 3, &mp_plat_print, &str); + mp_printf(&mp_plat_print, "%.*s\n", (int)len, str); + + mp_store_global(MP_QSTR_sys, mp_import_name(MP_QSTR_sys, mp_const_none, MP_OBJ_NEW_SMALL_INT(0))); + mp_repl_autocomplete("sys.", 4, &mp_plat_print, &str); + len = mp_repl_autocomplete("sys.impl", 8, &mp_plat_print, &str); + mp_printf(&mp_plat_print, "%.*s\n", (int)len, str); + } + + // attrtuple + { + mp_printf(&mp_plat_print, "# attrtuple\n"); + + static const qstr fields[] = {MP_QSTR_start, MP_QSTR_stop, MP_QSTR_step}; + static const mp_obj_t items[] = {MP_OBJ_NEW_SMALL_INT(1), MP_OBJ_NEW_SMALL_INT(2), MP_OBJ_NEW_SMALL_INT(3)}; + mp_obj_print_helper(&mp_plat_print, mp_obj_new_attrtuple(fields, 3, items), PRINT_REPR); + mp_printf(&mp_plat_print, "\n"); + } + + // str + { + mp_printf(&mp_plat_print, "# str\n"); + + // intern string + mp_printf(&mp_plat_print, "%d\n", mp_obj_is_qstr(mp_obj_str_intern(mp_obj_new_str("intern me", 9)))); + } + + // bytearray + { + mp_printf(&mp_plat_print, "# bytearray\n"); + + // create a bytearray via mp_obj_new_bytearray + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(mp_obj_new_bytearray(4, "data"), &bufinfo, MP_BUFFER_RW); + mp_printf(&mp_plat_print, "%.*s\n", bufinfo.len, bufinfo.buf); + } + + // mpz + { + mp_printf(&mp_plat_print, "# mpz\n"); + + mp_uint_t value; + mpz_t mpz; + mpz_init_zero(&mpz); + + // mpz_as_uint_checked, with success + mpz_set_from_int(&mpz, 12345678); + mp_printf(&mp_plat_print, "%d\n", mpz_as_uint_checked(&mpz, &value)); + mp_printf(&mp_plat_print, "%d\n", (int)value); + + // mpz_as_uint_checked, with negative arg + mpz_set_from_int(&mpz, -1); + mp_printf(&mp_plat_print, "%d\n", mpz_as_uint_checked(&mpz, &value)); + + // mpz_as_uint_checked, with overflowing arg + mpz_set_from_int(&mpz, 1); + mpz_shl_inpl(&mpz, &mpz, 70); + mp_printf(&mp_plat_print, "%d\n", mpz_as_uint_checked(&mpz, &value)); + + // mpz_set_from_float with inf as argument + mpz_set_from_float(&mpz, 1.0 / 0.0); + mpz_as_uint_checked(&mpz, &value); + mp_printf(&mp_plat_print, "%d\n", (int)value); + + // mpz_set_from_float with 0 as argument + mpz_set_from_float(&mpz, 0); + mpz_as_uint_checked(&mpz, &value); + mp_printf(&mp_plat_print, "%d\n", (int)value); + + // mpz_set_from_float with 0fun_bc = &fun_bc; + code_state->ip = (const byte *)"\x00"; // just needed for an invalid opcode + code_state->sp = &code_state->state[0]; + code_state->exc_sp_idx = 0; + code_state->old_globals = NULL; + mp_vm_return_kind_t ret = mp_execute_bytecode(code_state, MP_OBJ_NULL); + mp_printf(&mp_plat_print, "%d %d\n", ret, mp_obj_get_type(code_state->state[0]) == &mp_type_NotImplementedError); + } + + // scheduler + { + mp_printf(&mp_plat_print, "# scheduler\n"); + + // lock scheduler + mp_sched_lock(); + + // schedule multiple callbacks; last one should fail + for (int i = 0; i < 5; ++i) { + mp_printf(&mp_plat_print, "sched(%d)=%d\n", i, mp_sched_schedule(MP_OBJ_FROM_PTR(&mp_builtin_print_obj), MP_OBJ_NEW_SMALL_INT(i))); + } + + // test nested locking/unlocking + mp_sched_lock(); + mp_sched_unlock(); + + // shouldn't do anything while scheduler is locked + mp_handle_pending(true); + + // unlock scheduler + mp_sched_unlock(); + mp_printf(&mp_plat_print, "unlocked\n"); + + // drain pending callbacks + while (mp_sched_num_pending()) { + mp_handle_pending(true); + } + + // setting the keyboard interrupt and raising it during mp_handle_pending + mp_keyboard_interrupt(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_handle_pending(true); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + // setting the keyboard interrupt (twice) and cancelling it during mp_handle_pending + mp_keyboard_interrupt(); + mp_keyboard_interrupt(); + mp_handle_pending(false); + + // setting keyboard interrupt and a pending event (intr should be handled first) + mp_sched_schedule(MP_OBJ_FROM_PTR(&mp_builtin_print_obj), MP_OBJ_NEW_SMALL_INT(10)); + mp_keyboard_interrupt(); + if (nlr_push(&nlr) == 0) { + mp_handle_pending(true); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + mp_handle_pending(true); + } + + // ringbuf + { + byte buf[100]; + ringbuf_t ringbuf = {buf, sizeof(buf), 0, 0}; + + mp_printf(&mp_plat_print, "# ringbuf\n"); + + // Single-byte put/get with empty ringbuf. + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + ringbuf_put(&ringbuf, 22); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_get(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + + // Two-byte put/get with empty ringbuf. + ringbuf_put16(&ringbuf, 0xaa55); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + + // Two-byte put with full ringbuf. + for (int i = 0; i < 99; ++i) { + ringbuf_put(&ringbuf, i); + } + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x11bb)); + // Two-byte put with one byte free. + ringbuf_get(&ringbuf); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x3377)); + ringbuf_get(&ringbuf); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0xcc99)); + for (int i = 0; i < 97; ++i) { + ringbuf_get(&ringbuf); + } + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + mp_printf(&mp_plat_print, "%d %d\n", ringbuf_free(&ringbuf), ringbuf_avail(&ringbuf)); + + // Two-byte put with wrap around on first byte: + ringbuf.iput = 0; + ringbuf.iget = 0; + for (int i = 0; i < 99; ++i) { + ringbuf_put(&ringbuf, i); + ringbuf_get(&ringbuf); + } + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x11bb)); + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + + // Two-byte put with wrap around on second byte: + ringbuf.iput = 0; + ringbuf.iget = 0; + for (int i = 0; i < 98; ++i) { + ringbuf_put(&ringbuf, i); + ringbuf_get(&ringbuf); + } + mp_printf(&mp_plat_print, "%d\n", ringbuf_put16(&ringbuf, 0x22ff)); + mp_printf(&mp_plat_print, "%04x\n", ringbuf_get16(&ringbuf)); + + // Two-byte get from empty ringbuf. + ringbuf.iput = 0; + ringbuf.iget = 0; + mp_printf(&mp_plat_print, "%d\n", ringbuf_get16(&ringbuf)); + + // Two-byte get from ringbuf with one byte available. + ringbuf.iput = 0; + ringbuf.iget = 0; + ringbuf_put(&ringbuf, 0xaa); + mp_printf(&mp_plat_print, "%d\n", ringbuf_get16(&ringbuf)); + } + + // pairheap + { + mp_printf(&mp_plat_print, "# pairheap\n"); + + // Basic case. + int t0[] = {0, 2, 1, 3}; + pairheap_test(MP_ARRAY_SIZE(t0), t0); + + // All pushed in reverse order. + int t1[] = {7, 6, 5, 4, 3, 2, 1, 0}; + pairheap_test(MP_ARRAY_SIZE(t1), t1); + + // Basic deletion. + int t2[] = {1, -1, -1, 1, 2, -2, 2, 3, -3}; + pairheap_test(MP_ARRAY_SIZE(t2), t2); + + // Deletion of first child that has next node (the -3). + int t3[] = {1, 2, 3, 4, -1, -3}; + pairheap_test(MP_ARRAY_SIZE(t3), t3); + + // Deletion of node that's not first child (the -2). + int t4[] = {1, 2, 3, 4, -2}; + pairheap_test(MP_ARRAY_SIZE(t4), t4); + + // Deletion of node that's not first child and has children (the -3). + int t5[] = {3, 4, 5, 1, 2, -3}; + pairheap_test(MP_ARRAY_SIZE(t5), t5); + } + + // mp_obj_is_type and derivatives + { + mp_printf(&mp_plat_print, "# mp_obj_is_type\n"); + + // mp_obj_is_bool accepts only booleans + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_bool(mp_const_true), mp_obj_is_bool(mp_const_false)); + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_bool(MP_OBJ_NEW_SMALL_INT(1)), mp_obj_is_bool(mp_const_none)); + + // mp_obj_is_integer accepts ints and booleans + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_integer(MP_OBJ_NEW_SMALL_INT(1)), mp_obj_is_integer(mp_obj_new_int_from_ll(1))); + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_integer(mp_const_true), mp_obj_is_integer(mp_const_false)); + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_integer(mp_obj_new_str("1", 1)), mp_obj_is_integer(mp_const_none)); + + // mp_obj_is_int accepts small int and object ints + mp_printf(&mp_plat_print, "%d %d\n", mp_obj_is_int(MP_OBJ_NEW_SMALL_INT(1)), mp_obj_is_int(mp_obj_new_int_from_ll(1))); + } + + mp_printf(&mp_plat_print, "# end coverage.c\n"); + + mp_obj_streamtest_t *s = m_new_obj(mp_obj_streamtest_t); + s->base.type = &mp_type_stest_fileio; + s->buf = NULL; + s->len = 0; + s->pos = 0; + s->error_code = 0; + mp_obj_streamtest_t *s2 = m_new_obj(mp_obj_streamtest_t); + s2->base.type = &mp_type_stest_textio2; + + // return a tuple of data for testing on the Python side + mp_obj_t items[] = {(mp_obj_t)&str_no_hash_obj, (mp_obj_t)&bytes_no_hash_obj, MP_OBJ_FROM_PTR(s), MP_OBJ_FROM_PTR(s2)}; + return mp_obj_new_tuple(MP_ARRAY_SIZE(items), items); +} +MP_DEFINE_CONST_FUN_OBJ_0(extra_coverage_obj, extra_coverage); + +#endif diff --git a/ports/wapy-unix/fatfs_port.c b/ports/wapy-unix/fatfs_port.c new file mode 100644 index 000000000..30f1959f5 --- /dev/null +++ b/ports/wapy-unix/fatfs_port.c @@ -0,0 +1,5 @@ +#include "lib/oofatfs/ff.h" + +DWORD get_fattime(void) { + return 0; +} diff --git a/ports/wapy-unix/fdfile.h b/ports/wapy-unix/fdfile.h new file mode 100644 index 000000000..69a9b6be4 --- /dev/null +++ b/ports/wapy-unix/fdfile.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * 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_FDFILE_H +#define MICROPY_INCLUDED_UNIX_FDFILE_H + +#include "py/obj.h" + +typedef struct _mp_obj_fdfile_t { + mp_obj_base_t base; + int fd; +} mp_obj_fdfile_t; + +extern const mp_obj_type_t mp_type_fileio; +extern const mp_obj_type_t mp_type_textio; + +#endif // MICROPY_INCLUDED_UNIX_FDFILE_H diff --git a/ports/wapy-unix/file.c b/ports/wapy-unix/file.c new file mode 100644 index 000000000..2ead15947 --- /dev/null +++ b/ports/wapy-unix/file.c @@ -0,0 +1,321 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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 +#include +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/builtin.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "fdfile.h" + +#if MICROPY_PY_IO && !MICROPY_VFS + +#if MICROPY_VFS_POSIX || MICROPY_VFS_POSIX_FILE + #error "need !MICROPY_VFS_POSIX and !MICROPY_VFS_POSIX_FILE" +#endif + +#ifdef _WIN32 +#define fsync _commit +#endif + +#ifdef MICROPY_CPYTHON_COMPAT +STATIC int check_fd_is_open(const mp_obj_fdfile_t *o) { + if (o->fd < 0) { + mp_raise_ValueError_o("I/O operation on closed file"); + return 1; + } + return 0; +} +#else +#define check_fd_is_open(o) +#endif + +extern const mp_obj_type_t mp_type_fileio; +extern const mp_obj_type_t mp_type_textio; + +STATIC void fdfile_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", mp_obj_get_type_str(self_in), self->fd); +} + +STATIC mp_uint_t fdfile_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + mp_int_t r = read(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + if (r == -1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + return r; +} + +STATIC mp_uint_t fdfile_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { + mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + #if MICROPY_PY_OS_DUPTERM + if (o->fd <= STDERR_FILENO) { + mp_hal_stdout_tx_strn(buf, size); + return size; + } + #endif + MP_THREAD_GIL_EXIT(); + mp_int_t r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + while (r == -1 && errno == EINTR) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + } + if (r == -1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + return r; +} + +STATIC mp_uint_t fdfile_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + switch (request) { + case MP_STREAM_SEEK: { + struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)arg; + MP_THREAD_GIL_EXIT(); + off_t off = lseek(o->fd, s->offset, s->whence); + MP_THREAD_GIL_ENTER(); + if (off == (off_t)-1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + s->offset = off; + return 0; + } + case MP_STREAM_FLUSH: + MP_THREAD_GIL_EXIT(); + int ret = fsync(o->fd); + MP_THREAD_GIL_ENTER(); + if (ret == -1) { + if (errno == EINVAL + && (o->fd == STDIN_FILENO || o->fd == STDOUT_FILENO || o->fd == STDERR_FILENO)) { + // fsync(stdin/stdout/stderr) may fail with EINVAL, but don't propagate that + // error out. Because data is not buffered by us, and stdin/out/err.flush() + // should just be a no-op. + return 0; + } + *errcode = errno; + return MP_STREAM_ERROR; + } + return 0; + case MP_STREAM_CLOSE: + MP_THREAD_GIL_EXIT(); + close(o->fd); + MP_THREAD_GIL_ENTER(); + #ifdef MICROPY_CPYTHON_COMPAT + o->fd = -1; + #endif + return 0; + case MP_STREAM_GET_FILENO: + return o->fd; + default: + *errcode = EINVAL; + return MP_STREAM_ERROR; + } +} + +STATIC mp_obj_t fdfile___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + return mp_stream_close(args[0]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fdfile___exit___obj, 4, 4, fdfile___exit__); + +STATIC mp_obj_t fdfile_fileno(mp_obj_t self_in) { + mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in); + if (check_fd_is_open(self)) { + return MP_OBJ_NULL; + } + return MP_OBJ_NEW_SMALL_INT(self->fd); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(fdfile_fileno_obj, fdfile_fileno); + +// Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO, +// but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor +STATIC const mp_arg_t file_open_args[] = { + { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r)} }, + { MP_QSTR_buffering, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, +}; +#define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args) + +STATIC mp_obj_t fdfile_open(const mp_obj_type_t *type, mp_arg_val_t *args) { + mp_obj_fdfile_t *o = m_new_obj(mp_obj_fdfile_t); + const char *mode_s = mp_obj_str_get_str(args[1].u_obj); + + int mode_rw = 0, mode_x = 0; + while (*mode_s) { + switch (*mode_s++) { + case 'r': + mode_rw = O_RDONLY; + break; + case 'w': + mode_rw = O_WRONLY; + mode_x = O_CREAT | O_TRUNC; + break; + case 'a': + mode_rw = O_WRONLY; + mode_x = O_CREAT | O_APPEND; + break; + case '+': + mode_rw = O_RDWR; + break; + #if MICROPY_PY_IO_FILEIO + // If we don't have io.FileIO, then files are in text mode implicitly + case 'b': + type = &mp_type_fileio; + break; + case 't': + type = &mp_type_textio; + break; + #endif + } + } + + o->base.type = type; + + mp_obj_t fid = args[0].u_obj; + + if (mp_obj_is_small_int(fid)) { + o->fd = MP_OBJ_SMALL_INT_VALUE(fid); + return MP_OBJ_FROM_PTR(o); + } + + const char *fname = mp_obj_str_get_str(fid); + MP_THREAD_GIL_EXIT(); + int fd = open(fname, mode_x | mode_rw, 0644); + MP_THREAD_GIL_ENTER(); + if (fd == -1) { + return mp_raise_OSError_o(errno); + } + o->fd = fd; + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t fdfile_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; + mp_arg_parse_all_kw_array(n_args, n_kw, args, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals); + return fdfile_open(type, arg_vals); +} + +STATIC const mp_rom_map_elem_t rawfile_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&fdfile_fileno_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) }, + { MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&fdfile___exit___obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table); + +#if MICROPY_PY_IO_FILEIO +STATIC const mp_stream_p_t fileio_stream_p = { + .read = fdfile_read, + .write = fdfile_write, + .ioctl = fdfile_ioctl, +}; + +const mp_obj_type_t mp_type_fileio = { + { &mp_type_type }, + .name = MP_QSTR_FileIO, + .print = fdfile_print, + .make_new = fdfile_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &fileio_stream_p, + .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict, +}; +#endif + +STATIC const mp_stream_p_t textio_stream_p = { + .read = fdfile_read, + .write = fdfile_write, + .ioctl = fdfile_ioctl, + .is_text = true, +}; + +const mp_obj_type_t mp_type_textio = { + { &mp_type_type }, + .name = MP_QSTR_TextIOWrapper, + .print = fdfile_print, + .make_new = fdfile_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &textio_stream_p, + .locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict, +}; + +// Factory function for I/O stream classes +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + // TODO: analyze buffering args and instantiate appropriate type + mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; + mp_arg_parse_all(n_args, args, kwargs, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals); + return fdfile_open(&mp_type_textio, arg_vals); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); + +const mp_obj_fdfile_t mp_sys_stdin_obj = { .base = {&mp_type_textio}, .fd = STDIN_FILENO }; +const mp_obj_fdfile_t mp_sys_stdout_obj = { .base = {&mp_type_textio}, .fd = STDOUT_FILENO }; +const mp_obj_fdfile_t mp_sys_stderr_obj = { .base = {&mp_type_textio}, .fd = STDERR_FILENO }; +#else +#error "error, need MICROPY_PY_IO && !MICROPY_VFS" +#endif // MICROPY_PY_IO && !MICROPY_VFS diff --git a/ports/wapy-unix/gccollect.c b/ports/wapy-unix/gccollect.c new file mode 100644 index 000000000..f0441e4ea --- /dev/null +++ b/ports/wapy-unix/gccollect.c @@ -0,0 +1,53 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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/mpstate.h" +#include "py/gc.h" + +#include "lib/utils/gchelper.h" + +#if MICROPY_ENABLE_GC + +void gc_collect(void) { + // gc_dump_info(); + + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + #if MICROPY_PY_THREAD + mp_thread_gc_others(); + #endif + #if MICROPY_EMIT_NATIVE + mp_unix_mark_exec(); + #endif + gc_collect_end(); + + // printf("-----\n"); + // gc_dump_info(); +} + +#endif // MICROPY_ENABLE_GC diff --git a/ports/wapy-unix/input.c b/ports/wapy-unix/input.c new file mode 100644 index 000000000..4a77d1b27 --- /dev/null +++ b/ports/wapy-unix/input.c @@ -0,0 +1,125 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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 +#include +#include + +#include "py/mpstate.h" +#include "py/mphal.h" +#include "input.h" + +#if MICROPY_USE_READLINE == 1 +#include "lib/mp-readline/readline.h" +#endif + +#if MICROPY_USE_READLINE == 0 +char *prompt(char *p) { + // simple read string + static char buf[256]; + fputs(p, stdout); + char *s = fgets(buf, sizeof(buf), stdin); + if (!s) { + return NULL; + } + int l = strlen(buf); + if (buf[l - 1] == '\n') { + buf[l - 1] = 0; + } else { + l++; + } + char *line = malloc(l); + memcpy(line, buf, l); + return line; +} +#endif + +void prompt_read_history(void) { + #if MICROPY_USE_READLINE_HISTORY + #if MICROPY_USE_READLINE == 1 + readline_init0(); // will clear history pointers + char *home = getenv("HOME"); + if (home != NULL) { + vstr_t vstr; + vstr_init(&vstr, 50); + vstr_printf(&vstr, "%s/.micropython.history", home); + int fd = open(vstr_null_terminated_str(&vstr), O_RDONLY); + if (fd != -1) { + vstr_reset(&vstr); + for (;;) { + char c; + int sz = read(fd, &c, 1); + if (sz < 0) { + if (errno == EINTR) { + continue; + } + break; + } + if (sz == 0 || c == '\n') { + readline_push_history(vstr_null_terminated_str(&vstr)); + if (sz == 0) { + break; + } + vstr_reset(&vstr); + } else { + vstr_add_byte(&vstr, c); + } + } + close(fd); + } + vstr_clear(&vstr); + } + #endif + #endif +} + +void prompt_write_history(void) { + #if MICROPY_USE_READLINE_HISTORY + #if MICROPY_USE_READLINE == 1 + char *home = getenv("HOME"); + if (home != NULL) { + vstr_t vstr; + vstr_init(&vstr, 50); + vstr_printf(&vstr, "%s/.micropython.history", home); + int fd = open(vstr_null_terminated_str(&vstr), O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (fd != -1) { + for (int i = MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist)) - 1; i >= 0; i--) { + const char *line = MP_STATE_PORT(readline_hist)[i]; + if (line != NULL) { + while (write(fd, line, strlen(line)) == -1 && errno == EINTR) { + } + while (write(fd, "\n", 1) == -1 && errno == EINTR) { + } + } + } + close(fd); + } + } + #endif + #endif +} diff --git a/ports/wapy-unix/input.h b/ports/wapy-unix/input.h new file mode 100644 index 000000000..a76b87e64 --- /dev/null +++ b/ports/wapy-unix/input.h @@ -0,0 +1,8 @@ +#ifndef MICROPY_INCLUDED_UNIX_INPUT_H +#define MICROPY_INCLUDED_UNIX_INPUT_H + +char *prompt(char *p); +void prompt_read_history(void); +void prompt_write_history(void); + +#endif // MICROPY_INCLUDED_UNIX_INPUT_H diff --git a/ports/wapy-unix/main.c b/ports/wapy-unix/main.c new file mode 100644 index 000000000..00b974c3c --- /dev/null +++ b/ports/wapy-unix/main.c @@ -0,0 +1,780 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2017 Paul Sokolovsky + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "py/compile.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/repl.h" +#include "py/gc.h" +#include "py/stackctrl.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "extmod/misc.h" +#include "extmod/vfs.h" +#include "extmod/vfs_posix.h" +#include "genhdr/mpversion.h" +#include "input.h" +mp_state_ctx_t mp_state_ctx; +// Command line options, with their defaults +STATIC bool compile_only = false; +STATIC uint emit_opt = MP_EMIT_OPT_NONE; + +#if MICROPY_ENABLE_GC +// Heap size of GC heap (if enabled) +// Make it larger on a 64 bit machine, because pointers are larger. +long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4); +#endif + +STATIC void stderr_print_strn(void *env, const char *str, size_t len) { + (void)env; + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, write(STDERR_FILENO, str, len), {}); + mp_uos_dupterm_tx_strn(str, len); +} + +const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; + +#define FORCED_EXIT (0x100) +// If exc is SystemExit, return value where FORCED_EXIT bit set, +// and lower 8 bits are SystemExit value. For all other exceptions, +// return 1. +STATIC int handle_uncaught_exception(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // None is an exit value of 0; an int is its value; anything else is 1 + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + } + + // Report all other exceptions + mp_obj_print_exception(&mp_stderr_print, MP_OBJ_FROM_PTR(exc)); + return 1; +} + +#define LEX_SRC_STR (1) +#define LEX_SRC_VSTR (2) +#define LEX_SRC_FILENAME (3) +#define LEX_SRC_STDIN (4) + +// Returns standard error codes: 0 for success, 1 for all other errors, +// except if FORCED_EXIT bit is set then script raised SystemExit and the +// value of the exit is in the lower 8 bits of the return value +STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_input_kind_t input_kind, bool is_repl) { + mp_hal_set_interrupt_char(CHAR_CTRL_C); + + { + // create lexer based on source kind + mp_lexer_t *lex; + if (source_kind == LEX_SRC_STR) { + const char *line = source; + lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, line, strlen(line), false); + } else if (source_kind == LEX_SRC_VSTR) { + const vstr_t *vstr = source; + lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, false); + } else if (source_kind == LEX_SRC_FILENAME) { + lex = mp_lexer_new_from_file((const char *)source); + } else { // LEX_SRC_STDIN + lex = mp_lexer_new_from_fd(MP_QSTR__lt_stdin_gt_, 0, false); + } + + if (lex == NULL) { + return handle_uncaught_exception(); + } + + qstr source_name = lex->source_name; + + #if MICROPY_PY___FILE__ + if (input_kind == MP_PARSE_FILE_INPUT) { + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + } + #endif + + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + + #if defined(MICROPY_UNIX_COVERAGE) + // allow to print the parse tree in the coverage build + if (mp_verbose_flag >= 3) { + printf("----------------\n"); + mp_parse_node_print(&mp_plat_print, parse_tree.root, 0); + printf("----------------\n"); + } + #endif + + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, is_repl); + + if (!compile_only && module_fun != MP_OBJ_NULL) { + // execute it + mp_obj_t ret = mp_call_function_0(module_fun); + // check for pending exception + if (ret != MP_OBJ_NULL && MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + } + } + + if (MP_STATE_THREAD(active_exception) != NULL) { + // uncaught exception + mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + return handle_uncaught_exception(); + } + + mp_hal_set_interrupt_char(-1); + mp_handle_pending(true); + return 0; + } +} + +#if MICROPY_USE_READLINE == 1 +#include "lib/mp-readline/readline.h" +#else +STATIC char *strjoin(const char *s1, int sep_char, const char *s2) { + int l1 = strlen(s1); + int l2 = strlen(s2); + char *s = malloc(l1 + l2 + 2); + memcpy(s, s1, l1); + if (sep_char != 0) { + s[l1] = sep_char; + l1 += 1; + } + memcpy(s + l1, s2, l2); + s[l1 + l2] = 0; + return s; +} +#endif + +STATIC int do_repl(void) { + mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " + MICROPY_PY_SYS_PLATFORM " version\nUse Ctrl-D to exit, Ctrl-E for paste mode\n"); + + #if MICROPY_USE_READLINE == 1 + + // use MicroPython supplied readline + + vstr_t line; + vstr_init(&line, 16); + for (;;) { + mp_hal_stdio_mode_raw(); + + input_restart: + vstr_reset(&line); +#if MICROPY_UNIXSCHEDULE + const char sched[] = "__module__ = __import__('sys').modules.get('micropython',None);__module__ and __module__.scheduler()"; + const char *sched_ptr = &sched[0]; + mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; + // req prompt first pass + int ret=-3; + + for (;;) { + ret = readline_select( &line, "~~> ", ret); + //fail + if (ret==-2) { + mp_hal_stdout_tx_str("stdin select error\n"); + //maybe resume with normal readline + ret = readline(&line, ">>> "); + } + if (ret==-1) { + //mp_hal_stdio_mode_orig(); + execute_from_lexer( LEX_SRC_STR, sched_ptr, parse_input_kind, false); + //mp_hal_stdio_mode_raw(); + } + + //got data + if (ret>=0) { + break; + } + } +#else + int ret = readline(&line, ">>> "); + mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT; +#endif + + if (ret == CHAR_CTRL_C) { + // cancel input + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // EOF + printf("\n"); + mp_hal_stdio_mode_orig(); + vstr_clear(&line); + return 0; + } else if (ret == CHAR_CTRL_E) { + // paste mode + mp_hal_stdout_tx_str("\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\n=== "); + vstr_reset(&line); + for (;;) { + char c = mp_hal_stdin_rx_chr(); + if (c == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\n"); + goto input_restart; + } else if (c == CHAR_CTRL_D) { + // end of input + mp_hal_stdout_tx_str("\n"); + break; + } else { + // add char to buffer and echo + vstr_add_byte(&line, c); + if (c == '\r') { + mp_hal_stdout_tx_str("\n=== "); + } else { + mp_hal_stdout_tx_strn(&c, 1); + } + } + } + parse_input_kind = MP_PARSE_FILE_INPUT; + } else if (line.len == 0) { + if (ret != 0) { + printf("\n"); + } + goto input_restart; + } else { + // got a line with non-zero length, see if it needs continuing + while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) { + vstr_add_byte(&line, '\n'); + ret = readline(&line, "... "); + if (ret == CHAR_CTRL_C) { + // cancel everything + printf("\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // stop entering compound statement + break; + } + } + } + + mp_hal_stdio_mode_orig(); + + ret = execute_from_lexer(LEX_SRC_VSTR, &line, parse_input_kind, true); + if (ret & FORCED_EXIT) { + return ret; + } + } + + #else + + // use simple readline + + for (;;) { + char *line = prompt(">>> "); + if (line == NULL) { + // EOF + return 0; + } + while (mp_repl_continue_with_input(line)) { + char *line2 = prompt("... "); + if (line2 == NULL) { + break; + } + char *line3 = strjoin(line, '\n', line2); + free(line); + free(line2); + line = line3; + } + + int ret = execute_from_lexer(LEX_SRC_STR, line, MP_PARSE_SINGLE_INPUT, true); + if (ret & FORCED_EXIT) { + return ret; + } + free(line); + } + + #endif +} + +STATIC int do_file(const char *file) { + return execute_from_lexer(LEX_SRC_FILENAME, file, MP_PARSE_FILE_INPUT, false); +} + +STATIC int do_str(const char *str) { + return execute_from_lexer(LEX_SRC_STR, str, MP_PARSE_FILE_INPUT, false); +} + +STATIC void print_help(char **argv) { + printf( + "usage: %s [] [-X ] [-c | -m | ]\n" + "Options:\n" + "-h : print this help message\n" + "-i : enable inspection via REPL after running command/module/file\n" + #if MICROPY_DEBUG_PRINTERS + "-v : verbose (trace various operations); can be multiple\n" + #endif + "-O[N] : apply bytecode optimizations of level N\n" + "\n" + "Implementation specific options (-X):\n", argv[0] + ); + int impl_opts_cnt = 0; + printf( + " compile-only -- parse and compile only\n" + #if MICROPY_EMIT_NATIVE + " emit={bytecode,native,viper} -- set the default code emitter\n" + #else + " emit=bytecode -- set the default code emitter\n" + #endif + ); + impl_opts_cnt++; + #if MICROPY_ENABLE_GC + printf( + " heapsize=[w][K|M] -- set the heap size for the GC (default %ld)\n" + , heap_size); + impl_opts_cnt++; + #endif + + if (impl_opts_cnt == 0) { + printf(" (none)\n"); + } +} + +STATIC int invalid_args(void) { + fprintf(stderr, "Invalid command line arguments. Use -h option for help.\n"); + return 1; +} + +// Process options which set interpreter init options +STATIC void pre_process_options(int argc, char **argv) { + for (int a = 1; a < argc; a++) { + if (argv[a][0] == '-') { + if (strcmp(argv[a], "-h") == 0) { + print_help(argv); + exit(0); + } + if (strcmp(argv[a], "-X") == 0) { + if (a + 1 >= argc) { + exit(invalid_args()); + } + if (0) { + } else if (strcmp(argv[a + 1], "compile-only") == 0) { + compile_only = true; + } else if (strcmp(argv[a + 1], "emit=bytecode") == 0) { + emit_opt = MP_EMIT_OPT_BYTECODE; + #if MICROPY_EMIT_NATIVE + } else if (strcmp(argv[a + 1], "emit=native") == 0) { + emit_opt = MP_EMIT_OPT_NATIVE_PYTHON; + } else if (strcmp(argv[a + 1], "emit=viper") == 0) { + emit_opt = MP_EMIT_OPT_VIPER; + #endif + #if MICROPY_ENABLE_GC + } else if (strncmp(argv[a + 1], "heapsize=", sizeof("heapsize=") - 1) == 0) { + char *end; + heap_size = strtol(argv[a + 1] + sizeof("heapsize=") - 1, &end, 0); + // Don't bring unneeded libc dependencies like tolower() + // If there's 'w' immediately after number, adjust it for + // target word size. Note that it should be *before* size + // suffix like K or M, to avoid confusion with kilowords, + // etc. the size is still in bytes, just can be adjusted + // for word size (taking 32bit as baseline). + bool word_adjust = false; + if ((*end | 0x20) == 'w') { + word_adjust = true; + end++; + } + if ((*end | 0x20) == 'k') { + heap_size *= 1024; + } else if ((*end | 0x20) == 'm') { + heap_size *= 1024 * 1024; + } else { + // Compensate for ++ below + --end; + } + if (*++end != 0) { + goto invalid_arg; + } + if (word_adjust) { + heap_size = heap_size * (sizeof(void *)) / 4; + } + // If requested size too small, we'll crash anyway + if (heap_size < 700) { + goto invalid_arg; + } +/* + } else if (strncmp(argv[a + 1], "alloc-max=", sizeof("alloc-max=") - 1) == 0) { + char *end; + extern unsigned gc_alloc_count_max; + gc_alloc_count_max = strtol(argv[a + 1] + sizeof("alloc-max=") - 1, &end, 0); + if (*end != 0) { + goto invalid_arg; + } +*/ + #endif + } else { + invalid_arg: + exit(invalid_args()); + } + a++; + } + } + } +} + +STATIC void set_sys_argv(char *argv[], int argc, int start_arg) { + for (int i = start_arg; i < argc; i++) { + mp_obj_list_append(mp_sys_argv, MP_OBJ_NEW_QSTR(qstr_from_str(argv[i]))); + } +} + +#ifdef _WIN32 +#define PATHLIST_SEP_CHAR ';' +#else +#define PATHLIST_SEP_CHAR ':' +#endif + +MP_NOINLINE int main_(int argc, char **argv); + +int main(int argc, char **argv) { + #if MICROPY_PY_THREAD + mp_thread_init(); + #endif + // We should capture stack top ASAP after start, and it should be + // captured guaranteedly before any other stack variables are allocated. + // For this, actual main (renamed main_) should not be inlined into + // this function. main_() itself may have other functions inlined (with + // their own stack variables), that's why we need this main/main_ split. + mp_stack_ctrl_init(); + return main_(argc, argv); +} + +MP_NOINLINE int main_(int argc, char **argv) { + #ifdef SIGPIPE + // Do not raise SIGPIPE, instead return EPIPE. Otherwise, e.g. writing + // to peer-closed socket will lead to sudden termination of MicroPython + // process. SIGPIPE is particularly nasty, because unix shell doesn't + // print anything for it, so the above looks like completely sudden and + // silent termination for unknown reason. Ignoring SIGPIPE is also what + // CPython does. Note that this may lead to problems using MicroPython + // scripts as pipe filters, but again, that's what CPython does. So, + // scripts which want to follow unix shell pipe semantics (where SIGPIPE + // means "pipe was requested to terminate, it's not an error"), should + // catch EPIPE themselves. + signal(SIGPIPE, SIG_IGN); + #endif + + mp_stack_set_limit(40000 * (sizeof(void *) / 4)); + + pre_process_options(argc, argv); + + #if MICROPY_ENABLE_GC + char *heap = malloc(heap_size); + gc_init(heap, heap + heap_size); + #endif + + #if MICROPY_ENABLE_PYSTACK + static mp_obj_t pystack[1024]; + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); + #endif + + mp_init(); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + + #if MICROPY_EMIT_NATIVE + // Set default emitter options + MP_STATE_VM(default_emit_opt) = emit_opt; + #else + (void)emit_opt; + #endif + + #if MICROPY_VFS_POSIX + { + // Mount the host FS at the root of our internal VFS + mp_obj_t args[2] = { + mp_type_vfs_posix.make_new(&mp_type_vfs_posix, 0, 0, NULL), + MP_OBJ_NEW_QSTR(MP_QSTR__slash_), + }; + mp_vfs_mount(2, args, (mp_map_t *)&mp_const_empty_map); + MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table); + } + #endif + + char *home = getenv("HOME"); + char *path = getenv("MICROPYPATH"); + if (path == NULL) { + #ifdef MICROPY_PY_SYS_PATH_DEFAULT + path = MICROPY_PY_SYS_PATH_DEFAULT; + #else + path = "~/.micropython/lib:/usr/lib/micropython"; + #endif + } + size_t path_num = 1; // [0] is for current dir (or base dir of the script) + if (*path == PATHLIST_SEP_CHAR) { + path_num++; + } + for (char *p = path; p != NULL; p = strchr(p, PATHLIST_SEP_CHAR)) { + path_num++; + if (p != NULL) { + p++; + } + } + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), path_num); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + mp_obj_t *path_items; + mp_obj_list_get(mp_sys_path, &path_num, &path_items); + path_items[0] = MP_OBJ_NEW_QSTR(MP_QSTR_); + { + char *p = path; + for (mp_uint_t i = 1; i < path_num; i++) { + char *p1 = strchr(p, PATHLIST_SEP_CHAR); + if (p1 == NULL) { + p1 = p + strlen(p); + } + if (p[0] == '~' && p[1] == '/' && home != NULL) { + // Expand standalone ~ to $HOME + int home_l = strlen(home); + vstr_t vstr; + vstr_init(&vstr, home_l + (p1 - p - 1) + 1); + vstr_add_strn(&vstr, home, home_l); + vstr_add_strn(&vstr, p + 1, p1 - p - 1); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + path_items[i] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr); + } else { + path_items[i] = mp_obj_new_str_via_qstr(p, p1 - p); + } + p = p1 + 1; + } + } + + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); + if (MP_STATE_THREAD(active_exception) != NULL) { + return handle_uncaught_exception(); + } + + #if defined(MICROPY_UNIX_COVERAGE) + { + MP_DECLARE_CONST_FUN_OBJ_0(extra_coverage_obj); + mp_store_global(QSTR_FROM_STR_STATIC("extra_coverage"), MP_OBJ_FROM_PTR(&extra_coverage_obj)); + } + #endif + + // Here is some example code to create a class and instance of that class. + // First is the Python, then the C code. + // + // class TestClass: + // pass + // test_obj = TestClass() + // 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)); + + /* + printf("bytes:\n"); + printf(" total %d\n", m_get_total_bytes_allocated()); + printf(" cur %d\n", m_get_current_bytes_allocated()); + printf(" peak %d\n", m_get_peak_bytes_allocated()); + */ + + const int NOTHING_EXECUTED = -2; + int ret = NOTHING_EXECUTED; + bool inspect = false; + for (int a = 1; a < argc; a++) { + if (argv[a][0] == '-') { + if (strcmp(argv[a], "-i") == 0) { + inspect = true; + } else if (strcmp(argv[a], "-c") == 0) { + if (a + 1 >= argc) { + return invalid_args(); + } + ret = do_str(argv[a + 1]); + if (ret & FORCED_EXIT) { + break; + } + a += 1; + } else if (strcmp(argv[a], "-m") == 0) { + if (a + 1 >= argc) { + return invalid_args(); + } + mp_obj_t import_args[4]; + import_args[0] = mp_obj_new_str(argv[a + 1], strlen(argv[a + 1])); + import_args[1] = import_args[2] = mp_const_none; + // Ask __import__ to handle imported module specially - set its __name__ + // to __main__, and also return this leaf module, not top-level package + // containing it. + import_args[3] = mp_const_false; + // TODO: https://docs.python.org/3/using/cmdline.html#cmdoption-m : + // "the first element of sys.argv will be the full path to + // the module file (while the module file is being located, + // the first element will be set to "-m")." + set_sys_argv(argv, argc, a + 1); + + mp_obj_t mod; + bool subpkg_tried = false; + + reimport: + mod = mp_builtin___import__(MP_ARRAY_SIZE(import_args), import_args); + if (mod == MP_OBJ_NULL) { + // uncaught exception + return handle_uncaught_exception() & 0xff; + } + + if (mp_obj_is_package(mod) && !subpkg_tried) { + subpkg_tried = true; + vstr_t vstr; + int len = strlen(argv[a + 1]); + vstr_init(&vstr, len + sizeof(".__main__")); + vstr_add_strn(&vstr, argv[a + 1], len); + vstr_add_strn(&vstr, ".__main__", sizeof(".__main__") - 1); + import_args[0] = mp_obj_new_str_from_vstr(&mp_type_str, &vstr); + goto reimport; + } + + ret = 0; + break; + } else if (strcmp(argv[a], "-X") == 0) { + a += 1; + #if MICROPY_DEBUG_PRINTERS + } else if (strcmp(argv[a], "-v") == 0) { + mp_verbose_flag++; + #endif + } else if (strncmp(argv[a], "-O", 2) == 0) { + if (unichar_isdigit(argv[a][2])) { + MP_STATE_VM(mp_optimise_value) = argv[a][2] & 0xf; + } else { + MP_STATE_VM(mp_optimise_value) = 0; + for (char *p = argv[a] + 1; *p && *p == 'O'; p++, MP_STATE_VM(mp_optimise_value)++) {; + } + } + } else { + return invalid_args(); + } + } else { + char *pathbuf = malloc(PATH_MAX); + char *basedir = realpath(argv[a], pathbuf); + if (basedir == NULL) { + mp_printf(&mp_stderr_print, "%s: can't open file '%s': [Errno %d] %s\n", argv[0], argv[a], errno, strerror(errno)); + // CPython exits with 2 in such case + ret = 2; + break; + } + + // Set base dir of the script as first entry in sys.path + char *p = strrchr(basedir, '/'); + path_items[0] = mp_obj_new_str_via_qstr(basedir, p - basedir); + free(pathbuf); + + set_sys_argv(argv, argc, a); + ret = do_file(argv[a]); + break; + } + } + + const char *inspect_env = getenv("MICROPYINSPECT"); + if (inspect_env && inspect_env[0] != '\0') { + inspect = true; + } + if (ret == NOTHING_EXECUTED || inspect) { + if (isatty(0)) { + prompt_read_history(); + ret = do_repl(); + prompt_write_history(); + } else { + ret = execute_from_lexer(LEX_SRC_STDIN, NULL, MP_PARSE_FILE_INPUT, false); + } + } + + #if MICROPY_PY_SYS_SETTRACE + MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; + #endif + + #if MICROPY_PY_SYS_ATEXIT + // Beware, the sys.settrace callback should be disabled before running sys.atexit. + if (mp_obj_is_callable(MP_STATE_VM(sys_exitfunc))) { + mp_call_function_0(MP_STATE_VM(sys_exitfunc)); + } + #endif + + #if MICROPY_PY_MICROPYTHON_MEM_INFO + if (mp_verbose_flag) { + mp_micropython_mem_info(0, NULL); + } + #endif + + #if MICROPY_PY_THREAD + mp_thread_deinit(); + #endif + + #if defined(MICROPY_UNIX_COVERAGE) + gc_sweep_all(); + #endif + + mp_deinit(); + + #if MICROPY_ENABLE_GC && !defined(NDEBUG) + // We don't really need to free memory since we are about to exit the + // process, but doing so helps to find memory leaks. + free(heap); + #endif + + //printf("total bytes = %d\n", m_get_total_bytes_allocated()); + return ret & 0xff; +} + +#if !MICROPY_VFS +uint mp_import_stat(const char *path) { + struct stat st; + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + return MP_IMPORT_STAT_DIR; + } else if (S_ISREG(st.st_mode)) { + return MP_IMPORT_STAT_FILE; + } + } + return MP_IMPORT_STAT_NO_EXIST; +} +#endif + +void nlr_jump_fail(void *val) { + fprintf(stderr, "FATAL: uncaught NLR %p\n", val); + exit(2); +} diff --git a/ports/wapy-unix/manifest.py b/ports/wapy-unix/manifest.py new file mode 100644 index 000000000..666b4c0ab --- /dev/null +++ b/ports/wapy-unix/manifest.py @@ -0,0 +1,2 @@ +freeze_as_mpy('$(MPY_DIR)/tools', 'upip.py') +freeze_as_mpy('$(MPY_DIR)/tools', 'upip_utarfile.py', opt=3) diff --git a/ports/wapy-unix/manifest_coverage.py b/ports/wapy-unix/manifest_coverage.py new file mode 100644 index 000000000..0c32d0857 --- /dev/null +++ b/ports/wapy-unix/manifest_coverage.py @@ -0,0 +1,2 @@ +freeze_as_str('coverage-frzstr') +freeze_as_mpy('coverage-frzmpy') diff --git a/ports/wapy-unix/modffi.c b/ports/wapy-unix/modffi.c new file mode 100644 index 000000000..5edd5b124 --- /dev/null +++ b/ports/wapy-unix/modffi.c @@ -0,0 +1,528 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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 + +#include "py/runtime.h" +#include "py/binary.h" +#include "py/mperrno.h" + +/* + * modffi uses character codes to encode a value type, based on "struct" + * module type codes, with some extensions and overridings. + * + * Extra/overridden typecodes: + * v - void, can be used only as return type + * P - const void*, pointer to read-only memory + * p - void*, meaning pointer to a writable memory (note that this + * clashes with struct's "p" as "Pascal string"). + * s - as argument, the same as "p", as return value, causes string + * to be allocated and returned, instead of pointer value. + * O - mp_obj_t, passed as is (mostly useful as a callback param) + * + * TODO: + * C - callback function + * + * Note: all constraint specified by typecode can be not enforced at this time, + * but may be later. + */ + +typedef struct _mp_obj_opaque_t { + mp_obj_base_t base; + void *val; +} mp_obj_opaque_t; + +typedef struct _mp_obj_ffimod_t { + mp_obj_base_t base; + void *handle; +} mp_obj_ffimod_t; + +typedef struct _mp_obj_ffivar_t { + mp_obj_base_t base; + void *var; + char type; +// ffi_type *type; +} mp_obj_ffivar_t; + +typedef struct _mp_obj_ffifunc_t { + mp_obj_base_t base; + void *func; + char rettype; + const char *argtypes; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_ffifunc_t; + +typedef struct _mp_obj_fficallback_t { + mp_obj_base_t base; + void *func; + ffi_closure *clo; + char rettype; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_fficallback_t; + +//STATIC const mp_obj_type_t opaque_type; +STATIC const mp_obj_type_t ffimod_type; +STATIC const mp_obj_type_t ffifunc_type; +STATIC const mp_obj_type_t fficallback_type; +STATIC const mp_obj_type_t ffivar_type; + +STATIC ffi_type *char2ffi_type(char c) { + switch (c) { + case 'b': + return &ffi_type_schar; + case 'B': + return &ffi_type_uchar; + case 'h': + return &ffi_type_sshort; + case 'H': + return &ffi_type_ushort; + case 'i': + return &ffi_type_sint; + case 'I': + return &ffi_type_uint; + case 'l': + return &ffi_type_slong; + case 'L': + return &ffi_type_ulong; + case 'q': + return &ffi_type_sint64; + case 'Q': + return &ffi_type_uint64; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': + return &ffi_type_float; + case 'd': + return &ffi_type_double; + #endif + case 'O': // mp_obj_t + case 'C': // (*)() + case 'P': // const void* + case 'p': // void* + case 's': + return &ffi_type_pointer; + case 'v': + return &ffi_type_void; + default: + return NULL; + } +} + +STATIC ffi_type *get_ffi_type(mp_obj_t o_in) { + if (mp_obj_is_str(o_in)) { + const char *s = mp_obj_str_get_str(o_in); + ffi_type *t = char2ffi_type(*s); + if (t != NULL) { + return t; + } + } + // TODO: Support actual libffi type objects + + mp_raise_TypeError_o("Unknown type"); + return NULL; +} + +STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) { + switch (type) { + case 's': { + const char *s = (const char *)(intptr_t)val; + if (!s) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); + } + case 'v': + return mp_const_none; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': { + union { ffi_arg ffi; + mp_float_t flt; + } val_union = { .ffi = val }; + return mp_obj_new_float(val_union.flt); + } + case 'd': { + double *p = (double *)&val; + return mp_obj_new_float(*p); + } + #endif + case 'O': + return (mp_obj_t)(intptr_t)val; + default: + return mp_obj_new_int(val); + } +} + +// FFI module + +STATIC void ffimod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->handle); +} + +STATIC mp_obj_t ffimod_close(mp_obj_t self_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + dlclose(self->handle); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(ffimod_close_obj, ffimod_close); + +STATIC mp_obj_t make_func(mp_obj_t rettype_in, void *func, mp_obj_t argtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + const char *argtypes = mp_obj_str_get_str(argtypes_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(argtypes_in)); + mp_obj_ffifunc_t *o = m_new_obj_var(mp_obj_ffifunc_t, ffi_type *, nparams); + o->base.type = &ffifunc_type; + + o->func = func; + o->rettype = *rettype; + o->argtypes = argtypes; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(argtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + if (o->params[i - 1] == NULL) { + return MP_OBJ_NULL; + } + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + return mp_raise_ValueError_o("Error in ffi_prep_cif"); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t ffimod_func(size_t n_args, const mp_obj_t *args) { + (void)n_args; // always 4 + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(args[0]); + const char *symname = mp_obj_str_get_str(args[2]); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + return mp_raise_OSError_o(MP_ENOENT); + } + return make_func(args[1], sym, args[3]); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ffimod_func_obj, 4, 4, ffimod_func); + +STATIC mp_obj_t mod_ffi_func(mp_obj_t rettype, mp_obj_t addr_in, mp_obj_t argtypes) { + void *addr = (void *)MP_OBJ_TO_PTR(mp_obj_int_get_truncated(addr_in)); + return make_func(rettype, addr, argtypes); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_func_obj, mod_ffi_func); + +STATIC void call_py_func(ffi_cif *cif, void *ret, void **args, void *func) { + mp_obj_t pyargs[cif->nargs]; + for (uint i = 0; i < cif->nargs; i++) { + pyargs[i] = mp_obj_new_int(*(mp_int_t *)args[i]); + } + mp_obj_t res = mp_call_function_n_kw(MP_OBJ_FROM_PTR(func), cif->nargs, 0, pyargs); + + if (res != mp_const_none) { + *(ffi_arg *)ret = mp_obj_int_get_truncated(res); + } +} + +STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t paramtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(paramtypes_in)); + mp_obj_fficallback_t *o = m_new_obj_var(mp_obj_fficallback_t, ffi_type *, nparams); + o->base.type = &fficallback_type; + + o->clo = ffi_closure_alloc(sizeof(ffi_closure), &o->func); + + o->rettype = *rettype; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(paramtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + if (o->params[i - 1] == NULL) { + return MP_OBJ_NULL; + } + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + return mp_raise_ValueError_o("Error in ffi_prep_cif"); + } + + res = ffi_prep_closure_loc(o->clo, &o->cif, call_py_func, MP_OBJ_TO_PTR(func_in), o->func); + if (res != FFI_OK) { + return mp_raise_ValueError_o("ffi_prep_closure_loc"); + } + + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_callback_obj, mod_ffi_callback); + +STATIC mp_obj_t ffimod_var(mp_obj_t self_in, mp_obj_t vartype_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *rettype = mp_obj_str_get_str(vartype_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + return mp_raise_OSError_o(MP_ENOENT); + } + mp_obj_ffivar_t *o = m_new_obj(mp_obj_ffivar_t); + o->base.type = &ffivar_type; + + o->var = sym; + o->type = *rettype; + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(ffimod_var_obj, ffimod_var); + +STATIC mp_obj_t ffimod_addr(mp_obj_t self_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + return mp_raise_OSError_o(MP_ENOENT); + } + return mp_obj_new_int((uintptr_t)sym); +} +MP_DEFINE_CONST_FUN_OBJ_2(ffimod_addr_obj, ffimod_addr); + +STATIC mp_obj_t ffimod_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)n_args; + (void)n_kw; + + const char *fname = NULL; + if (args[0] != mp_const_none) { + fname = mp_obj_str_get_str(args[0]); + } + void *mod = dlopen(fname, RTLD_NOW | RTLD_LOCAL); + + if (mod == NULL) { + return mp_raise_OSError_o(errno); + } + mp_obj_ffimod_t *o = m_new_obj(mp_obj_ffimod_t); + o->base.type = type; + o->handle = mod; + return MP_OBJ_FROM_PTR(o); +} + +STATIC const mp_rom_map_elem_t ffimod_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&ffimod_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_var), MP_ROM_PTR(&ffimod_var_obj) }, + { MP_ROM_QSTR(MP_QSTR_addr), MP_ROM_PTR(&ffimod_addr_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&ffimod_close_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffimod_locals_dict, ffimod_locals_dict_table); + +STATIC const mp_obj_type_t ffimod_type = { + { &mp_type_type }, + .name = MP_QSTR_ffimod, + .print = ffimod_print, + .make_new = ffimod_make_new, + .locals_dict = (mp_obj_dict_t *)&ffimod_locals_dict, +}; + +// FFI function + +STATIC void ffifunc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)n_kw; + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + assert(n_kw == 0); + assert(n_args == self->cif.nargs); + + ffi_arg values[n_args]; + void *valueptrs[n_args]; + const char *argtype = self->argtypes; + for (uint i = 0; i < n_args; i++, argtype++) { + mp_obj_t a = args[i]; + if (*argtype == 'O') { + values[i] = (ffi_arg)(intptr_t)a; + #if MICROPY_PY_BUILTINS_FLOAT + } else if (*argtype == 'f') { + float *p = (float *)&values[i]; + *p = (float)mp_obj_get_float(a); + } else if (*argtype == 'd') { + double *p = (double *)&values[i]; + *p = mp_obj_get_float(a); + #endif + } else if (a == mp_const_none) { + values[i] = 0; + } else if (mp_obj_is_int(a)) { + values[i] = mp_obj_int_get_truncated(a); + } else if (mp_obj_is_str(a)) { + const char *s = mp_obj_str_get_str(a); + values[i] = (ffi_arg)(intptr_t)s; + } else if (((mp_obj_base_t *)MP_OBJ_TO_PTR(a))->type->buffer_p.get_buffer != NULL) { + mp_obj_base_t *o = (mp_obj_base_t *)MP_OBJ_TO_PTR(a); + mp_buffer_info_t bufinfo; + int ret = o->type->buffer_p.get_buffer(MP_OBJ_FROM_PTR(o), &bufinfo, MP_BUFFER_READ); // TODO: MP_BUFFER_READ? + if (ret != 0) { + goto error; + } + values[i] = (ffi_arg)(intptr_t)bufinfo.buf; + } else if (mp_obj_is_type(a, &fficallback_type)) { + mp_obj_fficallback_t *p = MP_OBJ_TO_PTR(a); + values[i] = (ffi_arg)(intptr_t)p->func; + } else { + goto error; + } + valueptrs[i] = &values[i]; + } + + // If ffi_arg is not big enough to hold a double, then we must pass along a + // pointer to a memory location of the correct size. + // TODO check if this needs to be done for other types which don't fit into + // ffi_arg. + #if MICROPY_PY_BUILTINS_FLOAT + if (sizeof(ffi_arg) == 4 && self->rettype == 'd') { + double retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return mp_obj_new_float(retval); + } else + #endif + { + ffi_arg retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return return_ffi_value(retval, self->rettype); + } + +error: + return mp_raise_TypeError_o("Don't know how to pass object to native function"); +} + +STATIC const mp_obj_type_t ffifunc_type = { + { &mp_type_type }, + .name = MP_QSTR_ffifunc, + .print = ffifunc_print, + .call = ffifunc_call, +}; + +// FFI callback for Python function + +STATIC void fficallback_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fficallback_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC const mp_obj_type_t fficallback_type = { + { &mp_type_type }, + .name = MP_QSTR_fficallback, + .print = fficallback_print, +}; + +// FFI variable + +STATIC void ffivar_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + // Variable value printed as cast to int + mp_printf(print, "", self->var, *(int *)self->var); +} + +STATIC mp_obj_t ffivar_get(mp_obj_t self_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + return mp_binary_get_val_array(self->type, self->var, 0); +} +MP_DEFINE_CONST_FUN_OBJ_1(ffivar_get_obj, ffivar_get); + +STATIC mp_obj_t ffivar_set(mp_obj_t self_in, mp_obj_t val_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + mp_binary_set_val_array(self->type, self->var, 0, val_in); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(ffivar_set_obj, ffivar_set); + +STATIC const mp_rom_map_elem_t ffivar_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&ffivar_get_obj) }, + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&ffivar_set_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffivar_locals_dict, ffivar_locals_dict_table); + +STATIC const mp_obj_type_t ffivar_type = { + { &mp_type_type }, + .name = MP_QSTR_ffivar, + .print = ffivar_print, + .locals_dict = (mp_obj_dict_t *)&ffivar_locals_dict, +}; + +// Generic opaque storage object (unused) + +/* +STATIC const mp_obj_type_t opaque_type = { + { &mp_type_type }, + .name = MP_QSTR_opaqueval, +// .print = opaque_print, +}; +*/ + +STATIC mp_obj_t mod_ffi_open(size_t n_args, const mp_obj_t *args) { + return ffimod_make_new(&ffimod_type, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_ffi_open_obj, 1, 2, mod_ffi_open); + +STATIC mp_obj_t mod_ffi_as_bytearray(mp_obj_t ptr, mp_obj_t size) { + return mp_obj_new_bytearray_by_ref(mp_obj_int_get_truncated(size), (void *)(uintptr_t)mp_obj_int_get_truncated(ptr)); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_ffi_as_bytearray_obj, mod_ffi_as_bytearray); + +STATIC const mp_rom_map_elem_t mp_module_ffi_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ffi) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mod_ffi_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_callback), MP_ROM_PTR(&mod_ffi_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&mod_ffi_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_as_bytearray), MP_ROM_PTR(&mod_ffi_as_bytearray_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ffi_globals, mp_module_ffi_globals_table); + +const mp_obj_module_t mp_module_ffi = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_ffi_globals, +}; diff --git a/ports/wapy-unix/modjni.c b/ports/wapy-unix/modjni.c new file mode 100644 index 000000000..74e7221de --- /dev/null +++ b/ports/wapy-unix/modjni.c @@ -0,0 +1,719 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Paul Sokolovsky + * + * 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/binary.h" + +#include + +#define JJ(call, ...) (*env)->call(env, __VA_ARGS__) +#define JJ1(call) (*env)->call(env) +#define MATCH(s, static) (!strncmp(s, static, sizeof(static) - 1)) + +static JavaVM *jvm; +static JNIEnv *env; +static jclass Class_class; +static jclass String_class; +static jmethodID Class_getName_mid; +static jmethodID Class_getField_mid; +static jmethodID Class_getMethods_mid; +static jmethodID Class_getConstructors_mid; +static jmethodID Method_getName_mid; +static jmethodID Object_toString_mid; + +static jclass List_class; +static jmethodID List_get_mid; +static jmethodID List_set_mid; +static jmethodID List_size_mid; + +static jclass IndexException_class; + +STATIC const mp_obj_type_t jobject_type; +STATIC const mp_obj_type_t jmethod_type; + +STATIC mp_obj_t new_jobject(jobject jo); +STATIC mp_obj_t new_jclass(jclass jc); +STATIC mp_obj_t call_method(jobject obj, const char *name, jarray methods, bool is_constr, size_t n_args, const mp_obj_t *args); +STATIC bool py2jvalue(const char **jtypesig, mp_obj_t arg, jvalue *out); + +typedef struct _mp_obj_jclass_t { + mp_obj_base_t base; + jclass cls; +} mp_obj_jclass_t; + +typedef struct _mp_obj_jobject_t { + mp_obj_base_t base; + jobject obj; +} mp_obj_jobject_t; + +typedef struct _mp_obj_jmethod_t { + mp_obj_base_t base; + jobject obj; + jmethodID meth; + qstr name; + bool is_static; +} mp_obj_jmethod_t; + +// Utility functions + +STATIC bool is_object_type(const char *jtypesig) { + while (*jtypesig != ' ' && *jtypesig) { + if (*jtypesig == '.') { + return true; + } + jtypesig++; + } + return false; +} + +STATIC void check_exception(void) { + jobject exc = JJ1(ExceptionOccurred); + if (exc) { + // JJ1(ExceptionDescribe); + mp_obj_t py_e = new_jobject(exc); + JJ1(ExceptionClear); + if (JJ(IsInstanceOf, exc, IndexException_class)) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_IndexError, py_e)); + } + nlr_raise(mp_obj_new_exception_arg1(&mp_type_Exception, py_e)); + } +} + +STATIC void print_jobject(const mp_print_t *print, jobject obj) { + jobject str_o = JJ(CallObjectMethod, obj, Object_toString_mid); + const char *str = JJ(GetStringUTFChars, str_o, NULL); + mp_printf(print, str); + JJ(ReleaseStringUTFChars, str_o, str); +} + +// jclass + +STATIC void jclass_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_jclass_t *self = MP_OBJ_TO_PTR(self_in); + if (kind == PRINT_REPR) { + mp_printf(print, "cls); + } + print_jobject(print, self->cls); + if (kind == PRINT_REPR) { + mp_printf(print, "\">"); + } +} + +STATIC void jclass_attr(mp_obj_t self_in, qstr attr_in, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + // load attribute + mp_obj_jclass_t *self = MP_OBJ_TO_PTR(self_in); + const char *attr = qstr_str(attr_in); + + jstring field_name = JJ(NewStringUTF, attr); + jobject field = JJ(CallObjectMethod, self->cls, Class_getField_mid, field_name); + if (!JJ1(ExceptionCheck)) { + jfieldID field_id = JJ(FromReflectedField, field); + jobject obj = JJ(GetStaticObjectField, self->cls, field_id); + dest[0] = new_jobject(obj); + return; + } + // JJ1(ExceptionDescribe); + JJ1(ExceptionClear); + + mp_obj_jmethod_t *o = m_new_obj(mp_obj_jmethod_t); + o->base.type = &jmethod_type; + o->name = attr_in; + o->meth = NULL; + o->obj = self->cls; + o->is_static = true; + dest[0] = MP_OBJ_FROM_PTR(o); + } +} + +STATIC mp_obj_t jclass_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (n_kw != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("kwargs not supported")); + } + mp_obj_jclass_t *self = MP_OBJ_TO_PTR(self_in); + + jarray methods = JJ(CallObjectMethod, self->cls, Class_getConstructors_mid); + + return call_method(self->cls, NULL, methods, true, n_args, args); +} + +STATIC const mp_rom_map_elem_t jclass_locals_dict_table[] = { +// { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&ffivar_get_obj) }, +// { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&ffivar_set_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(jclass_locals_dict, jclass_locals_dict_table); + +STATIC const mp_obj_type_t jclass_type = { + { &mp_type_type }, + .name = MP_QSTR_jclass, + .print = jclass_print, + .attr = jclass_attr, + .call = jclass_call, + .locals_dict = (mp_obj_dict_t *)&jclass_locals_dict, +}; + +STATIC mp_obj_t new_jclass(jclass jc) { + mp_obj_jclass_t *o = m_new_obj(mp_obj_jclass_t); + o->base.type = &jclass_type; + o->cls = jc; + return MP_OBJ_FROM_PTR(o); +} + +// jobject + +STATIC void jobject_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + if (kind == PRINT_REPR) { + mp_printf(print, "obj); + } + print_jobject(print, self->obj); + if (kind == PRINT_REPR) { + mp_printf(print, "\">"); + } +} + +STATIC void jobject_attr(mp_obj_t self_in, qstr attr_in, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + // load attribute + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + + const char *attr = qstr_str(attr_in); + jclass obj_class = JJ(GetObjectClass, self->obj); + jstring field_name = JJ(NewStringUTF, attr); + jobject field = JJ(CallObjectMethod, obj_class, Class_getField_mid, field_name); + JJ(DeleteLocalRef, field_name); + JJ(DeleteLocalRef, obj_class); + if (!JJ1(ExceptionCheck)) { + jfieldID field_id = JJ(FromReflectedField, field); + JJ(DeleteLocalRef, field); + jobject obj = JJ(GetObjectField, self->obj, field_id); + dest[0] = new_jobject(obj); + return; + } + // JJ1(ExceptionDescribe); + JJ1(ExceptionClear); + + mp_obj_jmethod_t *o = m_new_obj(mp_obj_jmethod_t); + o->base.type = &jmethod_type; + o->name = attr_in; + o->meth = NULL; + o->obj = self->obj; + o->is_static = false; + dest[0] = MP_OBJ_FROM_PTR(o); + } +} + +STATIC void get_jclass_name(jobject obj, char *buf) { + jclass obj_class = JJ(GetObjectClass, obj); + jstring name = JJ(CallObjectMethod, obj_class, Class_getName_mid); + jint len = JJ(GetStringLength, name); + JJ(GetStringUTFRegion, name, 0, len, buf); + check_exception(); +} + +STATIC mp_obj_t jobject_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t idx = mp_obj_get_int(index); + char class_name[64]; + get_jclass_name(self->obj, class_name); + // printf("class: %s\n", class_name); + + if (class_name[0] == '[') { + if (class_name[1] == 'L' || class_name[1] == '[') { + if (value == MP_OBJ_NULL) { + // delete + assert(0); + } else if (value == MP_OBJ_SENTINEL) { + // load + jobject el = JJ(GetObjectArrayElement, self->obj, idx); + return new_jobject(el); + } else { + // store + jvalue jval; + const char *t = class_name + 1; + py2jvalue(&t, value, &jval); + JJ(SetObjectArrayElement, self->obj, idx, jval.l); + return mp_const_none; + } + } + mp_raise_NotImplementedError(NULL); + } + + if (!JJ(IsInstanceOf, self->obj, List_class)) { + return MP_OBJ_NULL; + } + + + if (value == MP_OBJ_NULL) { + // delete + assert(0); + } else if (value == MP_OBJ_SENTINEL) { + // load + jobject el = JJ(CallObjectMethod, self->obj, List_get_mid, idx); + check_exception(); + return new_jobject(el); + } else { + // store + assert(0); + } + + + return MP_OBJ_NULL; +} + +STATIC mp_obj_t jobject_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_jobject_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_BOOL: + case MP_UNARY_OP_LEN: { + jint len = JJ(CallIntMethod, self->obj, List_size_mid); + if (op == MP_UNARY_OP_BOOL) { + return mp_obj_new_bool(len != 0); + } + return MP_OBJ_NEW_SMALL_INT(len); + } + default: + return MP_OBJ_NULL; // op not supported + } +} + +// TODO: subscr_load_adaptor & subscr_getiter convenience functions +// should be moved to common location for reuse. +STATIC mp_obj_t subscr_load_adaptor(mp_obj_t self_in, mp_obj_t index_in) { + return mp_obj_subscr(self_in, index_in, MP_OBJ_SENTINEL); +} +MP_DEFINE_CONST_FUN_OBJ_2(subscr_load_adaptor_obj, subscr_load_adaptor); + +// .getiter special method which returns iterator which works in terms +// of object subscription. +STATIC mp_obj_t subscr_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + mp_obj_t dest[2] = {MP_OBJ_FROM_PTR(&subscr_load_adaptor_obj), self_in}; + return mp_obj_new_getitem_iter(dest, iter_buf); +} + +STATIC const mp_obj_type_t jobject_type = { + { &mp_type_type }, + .name = MP_QSTR_jobject, + .print = jobject_print, + .unary_op = jobject_unary_op, + .attr = jobject_attr, + .subscr = jobject_subscr, + .getiter = subscr_getiter, +// .locals_dict = (mp_obj_dict_t*)&jobject_locals_dict, +}; + +STATIC mp_obj_t new_jobject(jobject jo) { + if (jo == NULL) { + return mp_const_none; + } else if (JJ(IsInstanceOf, jo, String_class)) { + const char *s = JJ(GetStringUTFChars, jo, NULL); + mp_obj_t ret = mp_obj_new_str(s, strlen(s)); + JJ(ReleaseStringUTFChars, jo, s); + return ret; + } else if (JJ(IsInstanceOf, jo, Class_class)) { + return new_jclass(jo); + } else { + mp_obj_jobject_t *o = m_new_obj(mp_obj_jobject_t); + o->base.type = &jobject_type; + o->obj = jo; + return MP_OBJ_FROM_PTR(o); + } + +} + + +// jmethod + +STATIC void jmethod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_jmethod_t *self = MP_OBJ_TO_PTR(self_in); + // Variable value printed as cast to int + mp_printf(print, "", qstr_str(self->name)); +} + +#define IMATCH(s, static) ((!strncmp(s, static, sizeof(static) - 1)) && (s += sizeof(static) - 1)) + +#define CHECK_TYPE(java_type_name) \ + if (strncmp(arg_type, java_type_name, sizeof(java_type_name) - 1) != 0) { \ + return false; \ + } \ + arg_type += sizeof(java_type_name) - 1; + +STATIC const char *strprev(const char *s, char c) { + while (*s != c) { + s--; + } + return s; +} + +STATIC bool py2jvalue(const char **jtypesig, mp_obj_t arg, jvalue *out) { + const char *arg_type = *jtypesig; + mp_obj_type_t *type = mp_obj_get_type(arg); + + if (type == &mp_type_str) { + if (IMATCH(arg_type, "java.lang.String") || IMATCH(arg_type, "java.lang.Object")) { + out->l = JJ(NewStringUTF, mp_obj_str_get_str(arg)); + } else { + return false; + } + } else if (type == &mp_type_int) { + if (IMATCH(arg_type, "int") || IMATCH(arg_type, "long")) { + // TODO: Java long is 64-bit actually + out->j = mp_obj_get_int(arg); + } else { + return false; + } + } else if (type == &jobject_type) { + bool is_object = false; + const char *expected_type = arg_type; + while (1) { + if (isalpha(*arg_type)) { + } else if (*arg_type == '.') { + is_object = true; + } else { + break; + } + arg_type++; + } + if (!is_object) { + return false; + } + mp_obj_jobject_t *jo = MP_OBJ_TO_PTR(arg); + if (!MATCH(expected_type, "java.lang.Object")) { + char class_name[64]; + get_jclass_name(jo->obj, class_name); + // printf("Arg class: %s\n", class_name); + if (strcmp(class_name, expected_type) != 0) { + return false; + } + } + out->l = jo->obj; + } else if (type == &mp_type_bool) { + if (IMATCH(arg_type, "boolean")) { + out->z = arg == mp_const_true; + } else { + return false; + } + } else if (arg == mp_const_none) { + // printf("TODO: Check java arg type!!\n"); + while (isalpha(*arg_type) || *arg_type == '.') { + arg_type++; + } + out->l = NULL; + } else { + mp_raise_TypeError(MP_ERROR_TEXT("arg type not supported")); + } + + *jtypesig = arg_type; + return true; +} + +#if 0 +// jvalue is known to be union of jobject and friends. And yet from C's +// perspective, it's aggregate object which may require passing via stack +// instead of registers. Work that around by passing jobject and typecasting +// it. +STATIC mp_obj_t jvalue2py(const char *jtypesig, jobject arg) { + if (arg == NULL || MATCH(jtypesig, "void")) { + return mp_const_none; + } else if (MATCH(jtypesig, "boolean")) { + return mp_obj_new_bool((bool)arg); + } else if (MATCH(jtypesig, "int")) { + return mp_obj_new_int((mp_int_t)arg); + } else if (is_object_type(jtypesig)) { + // Non-primitive, object type + return new_jobject(arg); + } + + printf("Unknown return type: %s\n", jtypesig); + + return MP_OBJ_NULL; +} +#endif + +STATIC mp_obj_t call_method(jobject obj, const char *name, jarray methods, bool is_constr, size_t n_args, const mp_obj_t *args) { + jvalue jargs[n_args]; +// printf("methods=%p\n", methods); + jsize num_methods = JJ(GetArrayLength, methods); + for (int i = 0; i < num_methods; i++) { + jobject meth = JJ(GetObjectArrayElement, methods, i); + jobject name_o = JJ(CallObjectMethod, meth, Object_toString_mid); + const char *decl = JJ(GetStringUTFChars, name_o, NULL); + const char *arg_types = strchr(decl, '(') + 1; + // const char *arg_types_end = strchr(arg_types, ')'); +// printf("method[%d]=%p %s\n", i, meth, decl); + + const char *meth_name = NULL; + const char *ret_type = NULL; + if (!is_constr) { + meth_name = strprev(arg_types, '.') + 1; + ret_type = strprev(meth_name, ' ') - 1; + ret_type = strprev(ret_type, ' ') + 1; + + int name_len = strlen(name); + if (strncmp(name, meth_name, name_len /*arg_types - meth_name - 1*/) || meth_name[name_len] != '(' /*(*/) { + goto next_method; + } + } +// printf("method[%d]=%p %s\n", i, meth, decl); +// printf("!!!%s\n", arg_types); +// printf("name=%p meth_name=%s\n", name, meth_name); + + bool found = true; + for (size_t j = 0; j < n_args && *arg_types != ')'; j++) { + if (!py2jvalue(&arg_types, args[j], &jargs[j])) { + goto next_method; + } + + if (*arg_types == ',') { + arg_types++; + } + } + + if (*arg_types != ')') { + goto next_method; + } + + if (found) { +// printf("found!\n"); + jmethodID method_id = JJ(FromReflectedMethod, meth); + if (is_constr) { + JJ(ReleaseStringUTFChars, name_o, decl); + jobject res = JJ(NewObjectA, obj, method_id, jargs); + return new_jobject(res); + } else { + mp_obj_t ret; + if (MATCH(ret_type, "void")) { + JJ(CallVoidMethodA, obj, method_id, jargs); + check_exception(); + ret = mp_const_none; + } else if (MATCH(ret_type, "int")) { + jint res = JJ(CallIntMethodA, obj, method_id, jargs); + check_exception(); + ret = mp_obj_new_int(res); + } else if (MATCH(ret_type, "boolean")) { + jboolean res = JJ(CallBooleanMethodA, obj, method_id, jargs); + check_exception(); + ret = mp_obj_new_bool(res); + } else if (is_object_type(ret_type)) { + jobject res = JJ(CallObjectMethodA, obj, method_id, jargs); + check_exception(); + ret = new_jobject(res); + } else { + JJ(ReleaseStringUTFChars, name_o, decl); + mp_raise_TypeError(MP_ERROR_TEXT("can't handle return type")); + } + + JJ(ReleaseStringUTFChars, name_o, decl); + JJ(DeleteLocalRef, name_o); + JJ(DeleteLocalRef, meth); + return ret; + } + } + + next_method: + JJ(ReleaseStringUTFChars, name_o, decl); + JJ(DeleteLocalRef, name_o); + JJ(DeleteLocalRef, meth); + } + + mp_raise_TypeError(MP_ERROR_TEXT("method not found")); +} + + +STATIC mp_obj_t jmethod_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (n_kw != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("kwargs not supported")); + } + mp_obj_jmethod_t *self = MP_OBJ_TO_PTR(self_in); + + const char *name = qstr_str(self->name); +// jstring meth_name = JJ(NewStringUTF, name); + + jclass obj_class = self->obj; + if (!self->is_static) { + obj_class = JJ(GetObjectClass, self->obj); + } + jarray methods = JJ(CallObjectMethod, obj_class, Class_getMethods_mid); + + return call_method(self->obj, name, methods, false, n_args, args); +} + +STATIC const mp_obj_type_t jmethod_type = { + { &mp_type_type }, + .name = MP_QSTR_jmethod, + .print = jmethod_print, + .call = jmethod_call, +// .attr = jobject_attr, +// .locals_dict = (mp_obj_dict_t*)&jobject_locals_dict, +}; + +#ifdef __ANDROID__ +#define LIBJVM_SO "libdvm.so" +#else +#define LIBJVM_SO "libjvm.so" +#endif + +STATIC void create_jvm(void) { + JavaVMInitArgs args; + JavaVMOption options; + options.optionString = "-Djava.class.path=."; + args.version = JNI_VERSION_1_6; + args.nOptions = 1; + args.options = &options; + args.ignoreUnrecognized = 0; + + if (env) { + return; + } + + void *libjvm = dlopen(LIBJVM_SO, RTLD_NOW | RTLD_GLOBAL); + if (!libjvm) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("unable to load libjvm.so, use LD_LIBRARY_PATH")); + } + int (*_JNI_CreateJavaVM)(void *, void **, void *) = dlsym(libjvm, "JNI_CreateJavaVM"); + + int st = _JNI_CreateJavaVM(&jvm, (void **)&env, &args); + if (st < 0 || !env) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("unable to create JVM")); + } + + Class_class = JJ(FindClass, "java/lang/Class"); + jclass method_class = JJ(FindClass, "java/lang/reflect/Method"); + String_class = JJ(FindClass, "java/lang/String"); + + jclass Object_class = JJ(FindClass, "java/lang/Object"); + Object_toString_mid = JJ(GetMethodID, Object_class, "toString", + MP_ERROR_TEXT("()Ljava/lang/String;")); + + Class_getName_mid = (*env)->GetMethodID(env, Class_class, "getName", + MP_ERROR_TEXT("()Ljava/lang/String;")); + Class_getField_mid = (*env)->GetMethodID(env, Class_class, "getField", + MP_ERROR_TEXT("(Ljava/lang/String;)Ljava/lang/reflect/Field;")); + Class_getMethods_mid = (*env)->GetMethodID(env, Class_class, "getMethods", + MP_ERROR_TEXT("()[Ljava/lang/reflect/Method;")); + Class_getConstructors_mid = (*env)->GetMethodID(env, Class_class, "getConstructors", + MP_ERROR_TEXT("()[Ljava/lang/reflect/Constructor;")); + Method_getName_mid = (*env)->GetMethodID(env, method_class, "getName", + MP_ERROR_TEXT("()Ljava/lang/String;")); + + List_class = JJ(FindClass, "java/util/List"); + List_get_mid = JJ(GetMethodID, List_class, "get", + MP_ERROR_TEXT("(I)Ljava/lang/Object;")); + List_set_mid = JJ(GetMethodID, List_class, "set", + MP_ERROR_TEXT("(ILjava/lang/Object;)Ljava/lang/Object;")); + List_size_mid = JJ(GetMethodID, List_class, "size", + MP_ERROR_TEXT("()I")); + IndexException_class = JJ(FindClass, "java/lang/IndexOutOfBoundsException"); +} + +STATIC mp_obj_t mod_jni_cls(mp_obj_t cls_name_in) { + const char *cls_name = mp_obj_str_get_str(cls_name_in); + if (!env) { + create_jvm(); + } + jclass cls = JJ(FindClass, cls_name); + + mp_obj_jclass_t *o = m_new_obj(mp_obj_jclass_t); + o->base.type = &jclass_type; + o->cls = cls; + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_jni_cls_obj, mod_jni_cls); + +STATIC mp_obj_t mod_jni_array(mp_obj_t type_in, mp_obj_t size_in) { + if (!env) { + create_jvm(); + } + mp_int_t size = mp_obj_get_int(size_in); + jobject res = NULL; + + if (mp_obj_is_type(type_in, &jclass_type)) { + + mp_obj_jclass_t *jcls = MP_OBJ_TO_PTR(type_in); + res = JJ(NewObjectArray, size, jcls->cls, NULL); + + } else if (mp_obj_is_str(type_in)) { + const char *type = mp_obj_str_get_str(type_in); + switch (*type) { + case 'Z': + res = JJ(NewBooleanArray, size); + break; + case 'B': + res = JJ(NewByteArray, size); + break; + case 'C': + res = JJ(NewCharArray, size); + break; + case 'S': + res = JJ(NewShortArray, size); + break; + case 'I': + res = JJ(NewIntArray, size); + break; + case 'J': + res = JJ(NewLongArray, size); + break; + case 'F': + res = JJ(NewFloatArray, size); + break; + case 'D': + res = JJ(NewDoubleArray, size); + break; + } + + } + + return new_jobject(res); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_jni_array_obj, mod_jni_array); + + +STATIC mp_obj_t mod_jni_env(void) { + return mp_obj_new_int((mp_int_t)(uintptr_t)env); +} +MP_DEFINE_CONST_FUN_OBJ_0(mod_jni_env_obj, mod_jni_env); + +STATIC const mp_rom_map_elem_t mp_module_jni_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jni) }, + { MP_ROM_QSTR(MP_QSTR_cls), MP_ROM_PTR(&mod_jni_cls_obj) }, + { MP_ROM_QSTR(MP_QSTR_array), MP_ROM_PTR(&mod_jni_array_obj) }, + { MP_ROM_QSTR(MP_QSTR_env), MP_ROM_PTR(&mod_jni_env_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_jni_globals, mp_module_jni_globals_table); + +const mp_obj_module_t mp_module_jni = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_jni_globals, +}; diff --git a/ports/wapy-unix/modmachine.c b/ports/wapy-unix/modmachine.c new file mode 100644 index 000000000..a3992662f --- /dev/null +++ b/ports/wapy-unix/modmachine.c @@ -0,0 +1,122 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2015 Paul Sokolovsky + * + * 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/obj.h" + +#include "extmod/machine_mem.h" +#include "extmod/machine_pinbase.h" +#include "extmod/machine_signal.h" +#include "extmod/machine_pulse.h" + +#if MICROPY_PLAT_DEV_MEM +#include +#include +#include +#define MICROPY_PAGE_SIZE 4096 +#define MICROPY_PAGE_MASK (MICROPY_PAGE_SIZE - 1) +#endif + +#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); + if ((addr & (align - 1)) != 0) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg_varg( &mp_type_ValueError, MP_ERROR_TEXT("address %08x is not aligned to %d bytes"), addr, align)) ; +#else + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("address %08x is not aligned to %d bytes"), addr, align); +#endif + } + #if MICROPY_PLAT_DEV_MEM + { + // Not thread-safe + static int fd; + static uintptr_t last_base = (uintptr_t)-1; + static uintptr_t map_page; + if (!fd) { + int _fd = open("/dev/mem", O_RDWR | O_SYNC); + if (_fd == -1) { +#if NO_NLR + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno))); + return 0; +#else + mp_raise_OSError(errno); +#endif + } + fd = _fd; + } + + uintptr_t cur_base = addr & ~MICROPY_PAGE_MASK; + if (cur_base != last_base) { + map_page = (uintptr_t)mmap(NULL, MICROPY_PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, cur_base); + last_base = cur_base; + } + addr = map_page + (addr & MICROPY_PAGE_MASK); + } + #endif + + return addr; +} + +#ifdef MICROPY_UNIX_MACHINE_IDLE +STATIC mp_obj_t machine_idle(void) { + MICROPY_UNIX_MACHINE_IDLE + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); +#endif + +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_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) }, + + #ifdef MICROPY_UNIX_MACHINE_IDLE + { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, + #endif + + { MP_ROM_QSTR(MP_QSTR_PinBase), MP_ROM_PTR(&machine_pinbase_type) }, + { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, + #if MICROPY_PY_MACHINE_PULSE + { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + #endif +}; + +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, +}; + +#endif // MICROPY_PY_MACHINE diff --git a/ports/wapy-unix/modos.c b/ports/wapy-unix/modos.c new file mode 100644 index 000000000..fd4078144 --- /dev/null +++ b/ports/wapy-unix/modos.c @@ -0,0 +1,335 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Paul Sokolovsky + * Copyright (c) 2014-2018 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 +#include +#include +#include +#include +#include +#include "py/mpconfig.h" + +#include "py/runtime.h" +#include "py/objtuple.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "extmod/vfs.h" +#include "extmod/misc.h" + +#ifdef __ANDROID__ +#define USE_STATFS 1 +#endif + +STATIC mp_obj_t mod_os_stat(mp_obj_t path_in) { + struct stat sb; + const char *path = mp_obj_str_get_str(path_in); + + MP_THREAD_GIL_EXIT(); + int res = stat(path, &sb); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(res, errno); + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.st_mode); + t->items[1] = mp_obj_new_int_from_uint(sb.st_ino); + t->items[2] = mp_obj_new_int_from_uint(sb.st_dev); + t->items[3] = mp_obj_new_int_from_uint(sb.st_nlink); + t->items[4] = mp_obj_new_int_from_uint(sb.st_uid); + t->items[5] = mp_obj_new_int_from_uint(sb.st_gid); + t->items[6] = mp_obj_new_int_from_uint(sb.st_size); + t->items[7] = mp_obj_new_int_from_uint(sb.st_atime); + t->items[8] = mp_obj_new_int_from_uint(sb.st_mtime); + t->items[9] = mp_obj_new_int_from_uint(sb.st_ctime); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_stat_obj, mod_os_stat); + +#if MICROPY_PY_OS_STATVFS + +#if USE_STATFS +#include +#define STRUCT_STATVFS struct statfs +#define STATVFS statfs +#define F_FAVAIL sb.f_ffree +#define F_NAMEMAX sb.f_namelen +#define F_FLAG sb.f_flags +#else +#include +#define STRUCT_STATVFS struct statvfs +#define STATVFS statvfs +#define F_FAVAIL sb.f_favail +#define F_NAMEMAX sb.f_namemax +#define F_FLAG sb.f_flag +#endif + +STATIC mp_obj_t mod_os_statvfs(mp_obj_t path_in) { + STRUCT_STATVFS sb; + const char *path = mp_obj_str_get_str(path_in); + + MP_THREAD_GIL_EXIT(); + int res = STATVFS(path, &sb); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(res, errno); + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.f_bsize); + t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.f_frsize); + t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.f_blocks); + t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.f_bfree); + t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.f_bavail); + t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.f_files); + t->items[6] = MP_OBJ_NEW_SMALL_INT(sb.f_ffree); + t->items[7] = MP_OBJ_NEW_SMALL_INT(F_FAVAIL); + t->items[8] = MP_OBJ_NEW_SMALL_INT(F_FLAG); + t->items[9] = MP_OBJ_NEW_SMALL_INT(F_NAMEMAX); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_statvfs_obj, mod_os_statvfs); +#endif + +STATIC mp_obj_t mod_os_remove(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + // Note that POSIX requires remove() to be able to delete a directory + // too (act as rmdir()). This is POSIX extenstion to ANSI C semantics + // of that function. But Python remove() follows ANSI C, and explicitly + // required to raise exception on attempt to remove a directory. Thus, + // call POSIX unlink() here. + MP_THREAD_GIL_EXIT(); + int r = unlink(path); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO(r, errno); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_remove_obj, mod_os_remove); + +STATIC mp_obj_t mod_os_rename(mp_obj_t old_path_in, mp_obj_t new_path_in) { + const char *old_path = mp_obj_str_get_str(old_path_in); + const char *new_path = mp_obj_str_get_str(new_path_in); + + MP_THREAD_GIL_EXIT(); + int r = rename(old_path, new_path); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO(r, errno); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_os_rename_obj, mod_os_rename); + +STATIC mp_obj_t mod_os_rmdir(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + MP_THREAD_GIL_EXIT(); + int r = rmdir(path); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO(r, errno); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_rmdir_obj, mod_os_rmdir); + +STATIC mp_obj_t mod_os_system(mp_obj_t cmd_in) { + const char *cmd = mp_obj_str_get_str(cmd_in); + + MP_THREAD_GIL_EXIT(); + int r = system(cmd); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO(r, errno); + + return MP_OBJ_NEW_SMALL_INT(r); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_system_obj, mod_os_system); + +STATIC mp_obj_t mod_os_getenv(mp_obj_t var_in) { + const char *s = getenv(mp_obj_str_get_str(var_in)); + if (s == NULL) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_getenv_obj, mod_os_getenv); + +STATIC mp_obj_t mod_os_putenv(mp_obj_t key_in, mp_obj_t value_in) { + const char *key = mp_obj_str_get_str(key_in); + const char *value = mp_obj_str_get_str(value_in); + int ret; + + #if _WIN32 + ret = _putenv_s(key, value); + #else + ret = setenv(key, value, 1); + #endif + + if (ret == -1) { + mp_raise_OSError(errno); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_os_putenv_obj, mod_os_putenv); + +STATIC mp_obj_t mod_os_unsetenv(mp_obj_t key_in) { + const char *key = mp_obj_str_get_str(key_in); + int ret; + + #if _WIN32 + ret = _putenv_s(key, ""); + #else + ret = unsetenv(key); + #endif + + if (ret == -1) { + mp_raise_OSError(errno); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_unsetenv_obj, mod_os_unsetenv); + +STATIC mp_obj_t mod_os_mkdir(mp_obj_t path_in) { + // TODO: Accept mode param + const char *path = mp_obj_str_get_str(path_in); + MP_THREAD_GIL_EXIT(); + #ifdef _WIN32 + int r = mkdir(path); + #else + int r = mkdir(path, 0777); + #endif + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(r, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_mkdir_obj, mod_os_mkdir); + +typedef struct _mp_obj_listdir_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + DIR *dir; +} mp_obj_listdir_t; + +STATIC mp_obj_t listdir_next(mp_obj_t self_in) { + mp_obj_listdir_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->dir == NULL) { + goto done; + } + MP_THREAD_GIL_EXIT(); + struct dirent *dirent = readdir(self->dir); + if (dirent == NULL) { + closedir(self->dir); + MP_THREAD_GIL_ENTER(); + self->dir = NULL; + done: + return MP_OBJ_STOP_ITERATION; + } + MP_THREAD_GIL_ENTER(); + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); + t->items[0] = mp_obj_new_str(dirent->d_name, strlen(dirent->d_name)); + + #ifdef _DIRENT_HAVE_D_TYPE + #ifdef DTTOIF + t->items[1] = MP_OBJ_NEW_SMALL_INT(DTTOIF(dirent->d_type)); + #else + if (dirent->d_type == DT_DIR) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); + } else if (dirent->d_type == DT_REG) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG); + } else { + t->items[1] = MP_OBJ_NEW_SMALL_INT(dirent->d_type); + } + #endif + #else + // DT_UNKNOWN should have 0 value on any reasonable system + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); + #endif + + #ifdef _DIRENT_HAVE_D_INO + t->items[2] = MP_OBJ_NEW_SMALL_INT(dirent->d_ino); + #else + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); + #endif + return MP_OBJ_FROM_PTR(t); +} + +STATIC mp_obj_t mod_os_ilistdir(size_t n_args, const mp_obj_t *args) { + const char *path = "."; + if (n_args > 0) { + path = mp_obj_str_get_str(args[0]); + } + mp_obj_listdir_t *o = m_new_obj(mp_obj_listdir_t); + o->base.type = &mp_type_polymorph_iter; + MP_THREAD_GIL_EXIT(); + o->dir = opendir(path); + MP_THREAD_GIL_ENTER(); + o->iternext = listdir_next; + return MP_OBJ_FROM_PTR(o); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_ilistdir_obj, 0, 1, mod_os_ilistdir); + +STATIC mp_obj_t mod_os_errno(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return MP_OBJ_NEW_SMALL_INT(errno); + } + + errno = mp_obj_get_int(args[0]); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj, 0, 1, mod_os_errno); + +STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, + { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mod_os_stat_obj) }, + #if MICROPY_PY_OS_STATVFS + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mod_os_statvfs_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_system), MP_ROM_PTR(&mod_os_system_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mod_os_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&mod_os_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mod_os_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_putenv), MP_ROM_PTR(&mod_os_putenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_unsetenv), MP_ROM_PTR(&mod_os_unsetenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mod_os_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mod_os_ilistdir_obj) }, + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_os_globals, mp_module_os_globals_table); + +const mp_obj_module_t mp_module_os = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_os_globals, +}; diff --git a/ports/wapy-unix/modtermios.c b/ports/wapy-unix/modtermios.c new file mode 100644 index 000000000..7a578becb --- /dev/null +++ b/ports/wapy-unix/modtermios.c @@ -0,0 +1,150 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2015 Paul Sokolovsky + * + * 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 "py/objlist.h" +#include "py/runtime.h" +#include "py/mphal.h" + +STATIC mp_obj_t mod_termios_tcgetattr(mp_obj_t fd_in) { + struct termios term; + int fd = mp_obj_get_int(fd_in); + + int res = tcgetattr(fd, &term); + RAISE_ERRNO(res, errno); + + mp_obj_list_t *r = MP_OBJ_TO_PTR(mp_obj_new_list(7, NULL)); + r->items[0] = MP_OBJ_NEW_SMALL_INT(term.c_iflag); + r->items[1] = MP_OBJ_NEW_SMALL_INT(term.c_oflag); + r->items[2] = MP_OBJ_NEW_SMALL_INT(term.c_cflag); + r->items[3] = MP_OBJ_NEW_SMALL_INT(term.c_lflag); + r->items[4] = MP_OBJ_NEW_SMALL_INT(cfgetispeed(&term)); + r->items[5] = MP_OBJ_NEW_SMALL_INT(cfgetospeed(&term)); + + mp_obj_list_t *cc = MP_OBJ_TO_PTR(mp_obj_new_list(NCCS, NULL)); + r->items[6] = MP_OBJ_FROM_PTR(cc); + for (int i = 0; i < NCCS; i++) { + if (i == VMIN || i == VTIME) { + cc->items[i] = MP_OBJ_NEW_SMALL_INT(term.c_cc[i]); + } else { + // https://docs.python.org/3/library/termios.html says value is *string*, + // but no way unicode chars could be there, if c_cc is defined to be a + // a "char". But it's type is actually cc_t, which can be anything. + // TODO: For now, we still deal with it like that. + cc->items[i] = mp_obj_new_bytes((byte *)&term.c_cc[i], 1); + } + } + return MP_OBJ_FROM_PTR(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_termios_tcgetattr_obj, mod_termios_tcgetattr); + +STATIC mp_obj_t mod_termios_tcsetattr(mp_obj_t fd_in, mp_obj_t when_in, mp_obj_t attrs_in) { + struct termios term; + int fd = mp_obj_get_int(fd_in); + int when = mp_obj_get_int(when_in); + if (when == 0) { + // We don't export TCSANOW and friends to save on code space. Then + // common lazy sense says that passing 0 should be godo enough, and + // it is e.g. for glibc. But for other libc's it's not, so set just + // treat 0 as defauling to TCSANOW. + when = TCSANOW; + } + + assert(mp_obj_is_type(attrs_in, &mp_type_list)); + mp_obj_list_t *attrs = MP_OBJ_TO_PTR(attrs_in); + + term.c_iflag = mp_obj_get_int(attrs->items[0]); + term.c_oflag = mp_obj_get_int(attrs->items[1]); + term.c_cflag = mp_obj_get_int(attrs->items[2]); + term.c_lflag = mp_obj_get_int(attrs->items[3]); + + mp_obj_list_t *cc = MP_OBJ_TO_PTR(attrs->items[6]); + for (int i = 0; i < NCCS; i++) { + if (i == VMIN || i == VTIME) { + term.c_cc[i] = mp_obj_get_int(cc->items[i]); + } else { + term.c_cc[i] = *mp_obj_str_get_str(cc->items[i]); + } + } + + int res = cfsetispeed(&term, mp_obj_get_int(attrs->items[4])); + RAISE_ERRNO(res, errno); + res = cfsetospeed(&term, mp_obj_get_int(attrs->items[5])); + RAISE_ERRNO(res, errno); + + res = tcsetattr(fd, when, &term); + RAISE_ERRNO(res, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_termios_tcsetattr_obj, mod_termios_tcsetattr); + +STATIC mp_obj_t mod_termios_setraw(mp_obj_t fd_in) { + struct termios term; + int fd = mp_obj_get_int(fd_in); + int res = tcgetattr(fd, &term); + RAISE_ERRNO(res, errno); + + term.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + term.c_oflag = 0; + term.c_cflag = (term.c_cflag & ~(CSIZE | PARENB)) | CS8; + term.c_lflag = 0; + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; + res = tcsetattr(fd, TCSAFLUSH, &term); + RAISE_ERRNO(res, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_termios_setraw_obj, mod_termios_setraw); + +STATIC const mp_rom_map_elem_t mp_module_termios_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_termios) }, + { MP_ROM_QSTR(MP_QSTR_tcgetattr), MP_ROM_PTR(&mod_termios_tcgetattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_tcsetattr), MP_ROM_PTR(&mod_termios_tcsetattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_setraw), MP_ROM_PTR(&mod_termios_setraw_obj) }, + +#define C(name) { MP_ROM_QSTR(MP_QSTR_##name), MP_ROM_INT(name) } + C(TCSANOW), + + C(B9600), + #ifdef B57600 + C(B57600), + #endif + #ifdef B115200 + C(B115200), + #endif +#undef C +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_termios_globals, mp_module_termios_globals_table); + +const mp_obj_module_t mp_module_termios = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_termios_globals, +}; diff --git a/ports/wapy-unix/modtime.c b/ports/wapy-unix/modtime.c new file mode 120000 index 000000000..f11434665 --- /dev/null +++ b/ports/wapy-unix/modtime.c @@ -0,0 +1 @@ +../unix/modtime.c \ No newline at end of file diff --git a/ports/wapy-unix/moduos_vfs.c b/ports/wapy-unix/moduos_vfs.c new file mode 100644 index 000000000..6e4f352aa --- /dev/null +++ b/ports/wapy-unix/moduos_vfs.c @@ -0,0 +1,94 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 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 "extmod/vfs.h" +#include "extmod/vfs_posix.h" +#include "extmod/vfs_fat.h" +#include "extmod/vfs_lfs.h" + +#if MICROPY_VFS + +// These are defined in modos.c +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_getenv_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_putenv_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_unsetenv_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_system_obj); + +STATIC const mp_rom_map_elem_t uos_vfs_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos_vfs) }, + { MP_ROM_QSTR(MP_QSTR_sep), MP_ROM_QSTR(MP_QSTR__slash_) }, + + { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) }, + { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_putenv), MP_ROM_PTR(&mod_os_putenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_unsetenv), MP_ROM_PTR(&mod_os_unsetenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_system), MP_ROM_PTR(&mod_os_system_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) }, + + { 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_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_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) }, + { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove + + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif + + #if MICROPY_VFS_POSIX + { MP_ROM_QSTR(MP_QSTR_VfsPosix), MP_ROM_PTR(&mp_type_vfs_posix) }, + #endif + #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 +}; + +STATIC MP_DEFINE_CONST_DICT(uos_vfs_module_globals, uos_vfs_module_globals_table); + +const mp_obj_module_t mp_module_uos_vfs = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&uos_vfs_module_globals, +}; + +#endif // MICROPY_VFS diff --git a/ports/wapy-unix/moduselect.c b/ports/wapy-unix/moduselect.c new file mode 100644 index 000000000..400040c61 --- /dev/null +++ b/ports/wapy-unix/moduselect.c @@ -0,0 +1,363 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * Copyright (c) 2015-2017 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mpconfig.h" + +#if MICROPY_PY_USELECT_POSIX + +#include +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objtuple.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "fdfile.h" + +#define DEBUG 0 + +#if MICROPY_PY_SOCKET +extern const mp_obj_type_t mp_type_socket; +#endif + +// Flags for poll() +#define FLAG_ONESHOT (1) + +/// \class Poll - poll class + +typedef struct _mp_obj_poll_t { + mp_obj_base_t base; + unsigned short alloc; + unsigned short len; + struct pollfd *entries; + mp_obj_t *obj_map; + short iter_cnt; + short iter_idx; + int flags; + // callee-owned tuple + mp_obj_t ret_tuple; +} mp_obj_poll_t; + +STATIC int get_fd(mp_obj_t fdlike) { + if (mp_obj_is_obj(fdlike)) { + const mp_stream_p_t *stream_p = mp_get_stream_raise(fdlike, MP_STREAM_OP_IOCTL); + int err; + mp_uint_t res = stream_p->ioctl(fdlike, MP_STREAM_GET_FILENO, 0, &err); + if (res != MP_STREAM_ERROR) { + return res; + } + } + return mp_obj_get_int(fdlike); +} + +/// \method register(obj[, eventmask]) +STATIC mp_obj_t poll_register(size_t n_args, const mp_obj_t *args) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + bool is_fd = mp_obj_is_int(args[1]); + int fd = get_fd(args[1]); + + mp_uint_t flags; + if (n_args == 3) { + flags = mp_obj_get_int(args[2]); + } else { + flags = POLLIN | POLLOUT; + } + + struct pollfd *free_slot = NULL; + + struct pollfd *entry = self->entries; + for (int i = 0; i < self->len; i++, entry++) { + int entry_fd = entry->fd; + if (entry_fd == fd) { + entry->events = flags; + return mp_const_false; + } + if (entry_fd == -1) { + free_slot = entry; + } + } + + if (free_slot == NULL) { + if (self->len >= self->alloc) { + self->entries = m_renew(struct pollfd, self->entries, self->alloc, self->alloc + 4); + if (self->obj_map) { + self->obj_map = m_renew(mp_obj_t, self->obj_map, self->alloc, self->alloc + 4); + } + self->alloc += 4; + } + free_slot = &self->entries[self->len++]; + } + + if (!is_fd) { + if (self->obj_map == NULL) { + self->obj_map = m_new0(mp_obj_t, self->alloc); + } + self->obj_map[free_slot - self->entries] = args[1]; + } + + free_slot->fd = fd; + free_slot->events = flags; + free_slot->revents = 0; + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_register_obj, 2, 3, poll_register); + +/// \method unregister(obj) +STATIC mp_obj_t poll_unregister(mp_obj_t self_in, mp_obj_t obj_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + struct pollfd *entries = self->entries; + int fd = get_fd(obj_in); + for (int i = self->len - 1; i >= 0; i--) { + if (entries->fd == fd) { + entries->fd = -1; + if (self->obj_map) { + self->obj_map[entries - self->entries] = MP_OBJ_NULL; + } + break; + } + entries++; + } + + // TODO raise KeyError if obj didn't exist in map + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(poll_unregister_obj, poll_unregister); + +/// \method modify(obj, eventmask) +STATIC mp_obj_t poll_modify(mp_obj_t self_in, mp_obj_t obj_in, mp_obj_t eventmask_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + struct pollfd *entries = self->entries; + int fd = get_fd(obj_in); + for (int i = self->len - 1; i >= 0; i--) { + if (entries->fd == fd) { + entries->events = mp_obj_get_int(eventmask_in); + return mp_const_none; + } + entries++; + } + + // obj doesn't exist in poller + return mp_raise_OSError_o(MP_ENOENT); +} +MP_DEFINE_CONST_FUN_OBJ_3(poll_modify_obj, poll_modify); + +STATIC int poll_poll_internal(size_t n_args, const mp_obj_t *args) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + + // work out timeout (it's given already in ms) + int timeout = -1; + int flags = 0; + if (n_args >= 2) { + if (args[1] != mp_const_none) { + mp_int_t timeout_i = mp_obj_get_int(args[1]); + if (timeout_i >= 0) { + timeout = timeout_i; + } + } + if (n_args >= 3) { + flags = mp_obj_get_int(args[2]); + } + } + + self->flags = flags; + + MP_THREAD_GIL_EXIT(); + int n_ready = poll(self->entries, self->len, timeout); + MP_THREAD_GIL_ENTER(); + + RAISE_ERRNO_R(n_ready, errno, -1); + return n_ready; +} + +/// \method poll([timeout]) +/// Timeout is in milliseconds. +STATIC mp_obj_t poll_poll(size_t n_args, const mp_obj_t *args) { + int n_ready = poll_poll_internal(n_args, args); + + if (n_ready < 0) { + return MP_OBJ_NULL; + } + + if (n_ready == 0) { + return mp_const_empty_tuple; + } + + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + + mp_obj_list_t *ret_list = MP_OBJ_TO_PTR(mp_obj_new_list(n_ready, NULL)); + int ret_i = 0; + struct pollfd *entries = self->entries; + for (int i = 0; i < self->len; i++, entries++) { + if (entries->revents != 0) { + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + // If there's an object stored, return it, otherwise raw fd + if (self->obj_map && self->obj_map[i] != MP_OBJ_NULL) { + t->items[0] = self->obj_map[i]; + } else { + t->items[0] = MP_OBJ_NEW_SMALL_INT(entries->fd); + } + t->items[1] = MP_OBJ_NEW_SMALL_INT(entries->revents); + ret_list->items[ret_i++] = MP_OBJ_FROM_PTR(t); + if (self->flags & FLAG_ONESHOT) { + entries->events = 0; + } + } + } + + return MP_OBJ_FROM_PTR(ret_list); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_poll_obj, 1, 3, poll_poll); + +STATIC mp_obj_t poll_ipoll(size_t n_args, const mp_obj_t *args) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]); + + if (self->ret_tuple == MP_OBJ_NULL) { + self->ret_tuple = mp_obj_new_tuple(2, NULL); + } + + int n_ready = poll_poll_internal(n_args, args); + if (n_ready < 0) { + return MP_OBJ_NULL; + } + + self->iter_cnt = n_ready; + self->iter_idx = 0; + + return args[0]; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_ipoll_obj, 1, 3, poll_ipoll); + +STATIC mp_obj_t poll_iternext(mp_obj_t self_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->iter_cnt == 0) { + return MP_OBJ_STOP_ITERATION; + } + + self->iter_cnt--; + + struct pollfd *entries = self->entries + self->iter_idx; + for (int i = self->iter_idx; i < self->len; i++, entries++) { + self->iter_idx++; + if (entries->revents != 0) { + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(self->ret_tuple); + // If there's an object stored, return it, otherwise raw fd + if (self->obj_map && self->obj_map[i] != MP_OBJ_NULL) { + t->items[0] = self->obj_map[i]; + } else { + t->items[0] = MP_OBJ_NEW_SMALL_INT(entries->fd); + } + t->items[1] = MP_OBJ_NEW_SMALL_INT(entries->revents); + if (self->flags & FLAG_ONESHOT) { + entries->events = 0; + } + return MP_OBJ_FROM_PTR(t); + } + } + + assert(!"inconsistent number of poll active entries"); + self->iter_cnt = 0; + return MP_OBJ_STOP_ITERATION; +} + +#if DEBUG +STATIC mp_obj_t poll_dump(mp_obj_t self_in) { + mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in); + + struct pollfd *entries = self->entries; + for (int i = self->len - 1; i >= 0; i--) { + printf("fd: %d ev: %x rev: %x", entries->fd, entries->events, entries->revents); + if (self->obj_map) { + printf(" obj: %p", self->obj_map[entries - self->entries]); + } + printf("\n"); + entries++; + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(poll_dump_obj, poll_dump); +#endif + +STATIC const mp_rom_map_elem_t poll_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_register), MP_ROM_PTR(&poll_register_obj) }, + { MP_ROM_QSTR(MP_QSTR_unregister), MP_ROM_PTR(&poll_unregister_obj) }, + { MP_ROM_QSTR(MP_QSTR_modify), MP_ROM_PTR(&poll_modify_obj) }, + { MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&poll_poll_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipoll), MP_ROM_PTR(&poll_ipoll_obj) }, + #if DEBUG + { MP_ROM_QSTR(MP_QSTR_dump), MP_ROM_PTR(&poll_dump_obj) }, + #endif +}; +STATIC MP_DEFINE_CONST_DICT(poll_locals_dict, poll_locals_dict_table); + +STATIC const mp_obj_type_t mp_type_poll = { + { &mp_type_type }, + .name = MP_QSTR_poll, + .getiter = mp_identity_getiter, + .iternext = poll_iternext, + .locals_dict = (void *)&poll_locals_dict, +}; + +STATIC mp_obj_t select_poll(size_t n_args, const mp_obj_t *args) { + int alloc = 4; + if (n_args > 0) { + alloc = mp_obj_get_int(args[0]); + } + mp_obj_poll_t *poll = m_new_obj(mp_obj_poll_t); + poll->base.type = &mp_type_poll; + poll->entries = m_new(struct pollfd, alloc); + poll->alloc = alloc; + poll->len = 0; + poll->obj_map = NULL; + poll->iter_cnt = 0; + poll->ret_tuple = MP_OBJ_NULL; + return MP_OBJ_FROM_PTR(poll); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_select_poll_obj, 0, 1, select_poll); + +STATIC const mp_rom_map_elem_t mp_module_select_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uselect) }, + { MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&mp_select_poll_obj) }, + { MP_ROM_QSTR(MP_QSTR_POLLIN), MP_ROM_INT(POLLIN) }, + { MP_ROM_QSTR(MP_QSTR_POLLOUT), MP_ROM_INT(POLLOUT) }, + { MP_ROM_QSTR(MP_QSTR_POLLERR), MP_ROM_INT(POLLERR) }, + { MP_ROM_QSTR(MP_QSTR_POLLHUP), MP_ROM_INT(POLLHUP) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_select_globals, mp_module_select_globals_table); + +const mp_obj_module_t mp_module_uselect = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_select_globals, +}; + +#endif // MICROPY_PY_USELECT_POSIX diff --git a/ports/wapy-unix/modusocket.c b/ports/wapy-unix/modusocket.c new file mode 100644 index 000000000..e1f9a6b5d --- /dev/null +++ b/ports/wapy-unix/modusocket.c @@ -0,0 +1,667 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Paul Sokolovsky + * Copyright (c) 2014-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "py/objtuple.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "py/builtin.h" +#include "py/mphal.h" +#include "py/mpthread.h" + +/* + The idea of this module is to implement reasonable minimum of + socket-related functions to write typical clients and servers. + The module named "usocket" on purpose, to allow to make + Python-level module more (or fully) compatible with CPython + "socket", e.g.: + ---- socket.py ---- + from usocket import * + from socket_more_funcs import * + from socket_more_funcs2 import * + ------------------- + I.e. this module should stay lean, and more functions (if needed) + should be add to separate modules (C or Python level). + */ + +// This type must "inherit" from mp_obj_fdfile_t, i.e. matching subset of +// fields should have the same layout. +typedef struct _mp_obj_socket_t { + mp_obj_base_t base; + int fd; + bool blocking; +} mp_obj_socket_t; + +const mp_obj_type_t mp_type_socket; + +// Helper functions +static inline mp_obj_t mp_obj_from_sockaddr(const struct sockaddr *addr, socklen_t len) { + return mp_obj_new_bytes((const byte *)addr, len); +} + +STATIC mp_obj_socket_t *socket_new(int fd) { + mp_obj_socket_t *o = m_new_obj(mp_obj_socket_t); + o->base.type = &mp_type_socket; + o->fd = fd; + o->blocking = true; + return o; +} + + +STATIC void socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "<_socket %d>", self->fd); +} + +STATIC mp_uint_t socket_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + mp_obj_socket_t *o = MP_OBJ_TO_PTR(o_in); + ssize_t r; + MP_HAL_RETRY_SYSCALL(r, read(o->fd, buf, size), { + // On blocking socket, we get EAGAIN in case SO_RCVTIMEO/SO_SNDTIMEO + // timed out, and need to convert that to ETIMEDOUT. + if (err == EAGAIN && o->blocking) { + err = MP_ETIMEDOUT; + } + + *errcode = err; + return MP_STREAM_ERROR; + }); + return (mp_uint_t)r; +} + +STATIC mp_uint_t socket_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { + mp_obj_socket_t *o = MP_OBJ_TO_PTR(o_in); + ssize_t r; + MP_HAL_RETRY_SYSCALL(r, write(o->fd, buf, size), { + // On blocking socket, we get EAGAIN in case SO_RCVTIMEO/SO_SNDTIMEO + // timed out, and need to convert that to ETIMEDOUT. + if (err == EAGAIN && o->blocking) { + err = MP_ETIMEDOUT; + } + + *errcode = err; + return MP_STREAM_ERROR; + }); + return (mp_uint_t)r; +} + +STATIC mp_uint_t socket_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(o_in); + (void)arg; + switch (request) { + case MP_STREAM_CLOSE: + // There's a POSIX drama regarding return value of close in general, + // and EINTR error in particular. See e.g. + // http://lwn.net/Articles/576478/ + // http://austingroupbugs.net/view.php?id=529 + // The rationale MicroPython follows is that close() just releases + // file descriptor. If you're interested to catch I/O errors before + // closing fd, fsync() it. + MP_THREAD_GIL_EXIT(); + close(self->fd); + MP_THREAD_GIL_ENTER(); + return 0; + + case MP_STREAM_GET_FILENO: + return self->fd; + + default: + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } +} + +STATIC mp_obj_t socket_fileno(mp_obj_t self_in) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->fd); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_fileno_obj, socket_fileno); + +STATIC mp_obj_t socket_connect(mp_obj_t self_in, mp_obj_t addr_in) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(addr_in, &bufinfo, MP_BUFFER_READ); + MP_THREAD_GIL_EXIT(); + int r = connect(self->fd, (const struct sockaddr *)bufinfo.buf, bufinfo.len); + MP_THREAD_GIL_ENTER(); + int err = errno; + if (r == -1 && self->blocking && err == EINPROGRESS) { + // EINPROGRESS on a blocking socket means the operation timed out + err = MP_ETIMEDOUT; + } + RAISE_ERRNO(r, err); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_connect_obj, socket_connect); + +STATIC mp_obj_t socket_bind(mp_obj_t self_in, mp_obj_t addr_in) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(addr_in, &bufinfo, MP_BUFFER_READ); + MP_THREAD_GIL_EXIT(); + int r = bind(self->fd, (const struct sockaddr *)bufinfo.buf, bufinfo.len); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(r, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_bind_obj, socket_bind); + +STATIC mp_obj_t socket_listen(mp_obj_t self_in, mp_obj_t backlog_in) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + MP_THREAD_GIL_EXIT(); + int r = listen(self->fd, MP_OBJ_SMALL_INT_VALUE(backlog_in)); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(r, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_listen_obj, socket_listen); + +STATIC mp_obj_t socket_accept(mp_obj_t self_in) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + // sockaddr_storage isn't stack-friendly (129 bytes or so) + //struct sockaddr_storage addr; + byte addr[32]; + socklen_t addr_len = sizeof(addr); + MP_THREAD_GIL_EXIT(); + int fd = accept(self->fd, (struct sockaddr *)&addr, &addr_len); + MP_THREAD_GIL_ENTER(); + int err = errno; + if (fd == -1 && self->blocking && err == EAGAIN) { + // EAGAIN on a blocking socket means the operation timed out + err = MP_ETIMEDOUT; + } + RAISE_ERRNO(fd, err); + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + t->items[0] = MP_OBJ_FROM_PTR(socket_new(fd)); + t->items[1] = mp_obj_new_bytearray(addr_len, &addr); + + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_accept_obj, socket_accept); + +// Note: besides flag param, this differs from read() in that +// this does not swallow blocking errors (EAGAIN, EWOULDBLOCK) - +// these would be thrown as exceptions. +STATIC mp_obj_t socket_recv(size_t n_args, const mp_obj_t *args) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); + int sz = MP_OBJ_SMALL_INT_VALUE(args[1]); + int flags = 0; + + if (n_args > 2) { + flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + } + + byte *buf = m_new(byte, sz); + MP_THREAD_GIL_EXIT(); + int out_sz = recv(self->fd, buf, sz, flags); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(out_sz, errno); + + mp_obj_t ret = mp_obj_new_str_of_type(&mp_type_bytes, buf, out_sz); + m_del(char, buf, sz); + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_recv_obj, 2, 3, socket_recv); + +STATIC mp_obj_t socket_recvfrom(size_t n_args, const mp_obj_t *args) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); + int sz = MP_OBJ_SMALL_INT_VALUE(args[1]); + int flags = 0; + + if (n_args > 2) { + flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + } + + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + byte *buf = m_new(byte, sz); + MP_THREAD_GIL_EXIT(); + int out_sz = recvfrom(self->fd, buf, sz, flags, (struct sockaddr *)&addr, &addr_len); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(out_sz, errno); + + mp_obj_t buf_o = mp_obj_new_str_of_type(&mp_type_bytes, buf, out_sz); + m_del(char, buf, sz); + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + t->items[0] = buf_o; + t->items[1] = mp_obj_from_sockaddr((struct sockaddr *)&addr, addr_len); + + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_recvfrom_obj, 2, 3, socket_recvfrom); + +// Note: besides flag param, this differs from write() in that +// this does not swallow blocking errors (EAGAIN, EWOULDBLOCK) - +// these would be thrown as exceptions. +STATIC mp_obj_t socket_send(size_t n_args, const mp_obj_t *args) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); + int flags = 0; + + if (n_args > 2) { + flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + } + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); + MP_THREAD_GIL_EXIT(); + int out_sz = send(self->fd, bufinfo.buf, bufinfo.len, flags); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(out_sz, errno); + + return MP_OBJ_NEW_SMALL_INT(out_sz); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_send_obj, 2, 3, socket_send); + +STATIC mp_obj_t socket_sendto(size_t n_args, const mp_obj_t *args) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); + int flags = 0; + + mp_obj_t dst_addr = args[2]; + if (n_args > 3) { + flags = MP_OBJ_SMALL_INT_VALUE(args[2]); + dst_addr = args[3]; + } + + mp_buffer_info_t bufinfo, addr_bi; + mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); + mp_get_buffer_raise(dst_addr, &addr_bi, MP_BUFFER_READ); + MP_THREAD_GIL_EXIT(); + int out_sz = sendto(self->fd, bufinfo.buf, bufinfo.len, flags, + (struct sockaddr *)addr_bi.buf, addr_bi.len); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(out_sz, errno); + + return MP_OBJ_NEW_SMALL_INT(out_sz); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_sendto_obj, 3, 4, socket_sendto); + +STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { + (void)n_args; // always 4 + mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); + int level = MP_OBJ_SMALL_INT_VALUE(args[1]); + int option = mp_obj_get_int(args[2]); + + const void *optval; + socklen_t optlen; + int val; + if (mp_obj_is_int(args[3])) { + val = mp_obj_int_get_truncated(args[3]); + optval = &val; + optlen = sizeof(val); + } else { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + optval = bufinfo.buf; + optlen = bufinfo.len; + } + MP_THREAD_GIL_EXIT(); + int r = setsockopt(self->fd, level, option, optval, optlen); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(r, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_setsockopt_obj, 4, 4, socket_setsockopt); + +STATIC mp_obj_t socket_setblocking(mp_obj_t self_in, mp_obj_t flag_in) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + int val = mp_obj_is_true(flag_in); + int flags = fcntl(self->fd, F_GETFL, 0); + RAISE_ERRNO(flags, errno); + if (val) { + flags &= ~O_NONBLOCK; + } else { + flags |= O_NONBLOCK; + } + flags = fcntl(self->fd, F_SETFL, flags); + RAISE_ERRNO(flags, errno); + self->blocking = val; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_setblocking_obj, socket_setblocking); + +STATIC mp_obj_t socket_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) { + mp_obj_socket_t *self = MP_OBJ_TO_PTR(self_in); + struct timeval tv = {0,}; + bool new_blocking = true; + + // Timeout of None means no timeout, which in POSIX is signified with 0 timeout, + // and that's how 'tv' is initialized above + if (timeout_in != mp_const_none) { + #if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(timeout_in); + double ipart; + tv.tv_usec = (long int)round(modf(val, &ipart) * 1000000); + tv.tv_sec = (long int)ipart; + #else + tv.tv_sec = mp_obj_get_int(timeout_in); + #endif + + // For SO_RCVTIMEO/SO_SNDTIMEO, zero timeout means infinity, but + // for Python API it means non-blocking. + if (tv.tv_sec == 0 && tv.tv_usec == 0) { + new_blocking = false; + } + } + + if (new_blocking) { + int r; + MP_THREAD_GIL_EXIT(); + r = setsockopt(self->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)); + if (r == -1) { + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(r, errno); + } + r = setsockopt(self->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct timeval)); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(r, errno); + } + + if (self->blocking != new_blocking) { + socket_setblocking(self_in, mp_obj_new_bool(new_blocking)); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_settimeout_obj, socket_settimeout); + +STATIC mp_obj_t socket_makefile(size_t n_args, const mp_obj_t *args) { + // TODO: CPython explicitly says that closing returned object doesn't close + // the original socket (Python2 at all says that fd is dup()ed). But we + // save on the bloat. + mp_obj_socket_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_t *new_args = alloca(n_args * sizeof(mp_obj_t)); + memcpy(new_args + 1, args + 1, (n_args - 1) * sizeof(mp_obj_t)); + new_args[0] = MP_OBJ_NEW_SMALL_INT(self->fd); + return mp_builtin_open(n_args, new_args, (mp_map_t *)&mp_const_empty_map); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_makefile_obj, 1, 3, socket_makefile); + +STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + (void)n_kw; + + int family = AF_INET; + int type = SOCK_STREAM; + int proto = 0; + + if (n_args > 0) { + assert(mp_obj_is_small_int(args[0])); + family = MP_OBJ_SMALL_INT_VALUE(args[0]); + if (n_args > 1) { + assert(mp_obj_is_small_int(args[1])); + type = MP_OBJ_SMALL_INT_VALUE(args[1]); + if (n_args > 2) { + assert(mp_obj_is_small_int(args[2])); + proto = MP_OBJ_SMALL_INT_VALUE(args[2]); + } + } + } + MP_THREAD_GIL_EXIT(); + int fd = socket(family, type, proto); + MP_THREAD_GIL_ENTER(); + RAISE_ERRNO(fd, errno); + return MP_OBJ_FROM_PTR(socket_new(fd)); +} + +STATIC const mp_rom_map_elem_t usocket_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&socket_fileno_obj) }, + { MP_ROM_QSTR(MP_QSTR_makefile), MP_ROM_PTR(&socket_makefile_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&socket_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_bind), MP_ROM_PTR(&socket_bind_obj) }, + { MP_ROM_QSTR(MP_QSTR_listen), MP_ROM_PTR(&socket_listen_obj) }, + { MP_ROM_QSTR(MP_QSTR_accept), MP_ROM_PTR(&socket_accept_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&socket_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_recvfrom), MP_ROM_PTR(&socket_recvfrom_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&socket_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_sendto), MP_ROM_PTR(&socket_sendto_obj) }, + { MP_ROM_QSTR(MP_QSTR_setsockopt), MP_ROM_PTR(&socket_setsockopt_obj) }, + { MP_ROM_QSTR(MP_QSTR_setblocking), MP_ROM_PTR(&socket_setblocking_obj) }, + { MP_ROM_QSTR(MP_QSTR_settimeout), MP_ROM_PTR(&socket_settimeout_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(usocket_locals_dict, usocket_locals_dict_table); + +STATIC const mp_stream_p_t usocket_stream_p = { + .read = socket_read, + .write = socket_write, + .ioctl = socket_ioctl, +}; + +const mp_obj_type_t mp_type_socket = { + { &mp_type_type }, + .name = MP_QSTR_socket, + .print = socket_print, + .make_new = socket_make_new, + .getiter = NULL, + .iternext = NULL, + .protocol = &usocket_stream_p, + .locals_dict = (mp_obj_dict_t *)&usocket_locals_dict, +}; + +#define BINADDR_MAX_LEN sizeof(struct in6_addr) +STATIC mp_obj_t mod_socket_inet_pton(mp_obj_t family_in, mp_obj_t addr_in) { + int family = mp_obj_get_int(family_in); + byte binaddr[BINADDR_MAX_LEN]; + int r = inet_pton(family, mp_obj_str_get_str(addr_in), binaddr); + RAISE_ERRNO(r, errno); + if (r == 0) { + return mp_raise_OSError_o(MP_EINVAL); + } + int binaddr_len = 0; + switch (family) { + case AF_INET: + binaddr_len = sizeof(struct in_addr); + break; + case AF_INET6: + binaddr_len = sizeof(struct in6_addr); + break; + } + return mp_obj_new_bytes(binaddr, binaddr_len); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_socket_inet_pton_obj, mod_socket_inet_pton); + +STATIC mp_obj_t mod_socket_inet_ntop(mp_obj_t family_in, mp_obj_t binaddr_in) { + int family = mp_obj_get_int(family_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(binaddr_in, &bufinfo, MP_BUFFER_READ); + vstr_t vstr; + vstr_init_len(&vstr, family == AF_INET ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN); + if (inet_ntop(family, bufinfo.buf, vstr.buf, vstr.len) == NULL) { + return mp_raise_OSError_o(errno); + } + vstr.len = strlen(vstr.buf); + return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_socket_inet_ntop_obj, mod_socket_inet_ntop); + +STATIC mp_obj_t mod_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { + // TODO: Implement 5th and 6th args + + const char *host = mp_obj_str_get_str(args[0]); + const char *serv = NULL; + struct addrinfo hints; + char buf[6]; + memset(&hints, 0, sizeof(hints)); + // getaddrinfo accepts port in string notation, so however + // it may seem stupid, we need to convert int to str + if (mp_obj_is_small_int(args[1])) { + unsigned port = (unsigned short)MP_OBJ_SMALL_INT_VALUE(args[1]); + snprintf(buf, sizeof(buf), "%u", port); + serv = buf; + hints.ai_flags = AI_NUMERICSERV; + #ifdef __UCLIBC_MAJOR__ + #if __UCLIBC_MAJOR__ == 0 && (__UCLIBC_MINOR__ < 9 || (__UCLIBC_MINOR__ == 9 && __UCLIBC_SUBLEVEL__ <= 32)) +// "warning" requires -Wno-cpp which is a relatively new gcc option, so we choose not to use it. +//#warning Working around uClibc bug with numeric service name + // Older versions og uClibc have bugs when numeric ports in service + // arg require also hints.ai_socktype (or hints.ai_protocol) != 0 + // This actually was fixed in 0.9.32.1, but uClibc doesn't allow to + // test for that. + // http://git.uclibc.org/uClibc/commit/libc/inet/getaddrinfo.c?id=bc3be18145e4d5 + // Note that this is crude workaround, precluding UDP socket addresses + // to be returned. TODO: set only if not set by Python args. + hints.ai_socktype = SOCK_STREAM; + #endif + #endif + } else { + serv = mp_obj_str_get_str(args[1]); + } + + if (n_args > 2) { + hints.ai_family = MP_OBJ_SMALL_INT_VALUE(args[2]); + if (n_args > 3) { + hints.ai_socktype = MP_OBJ_SMALL_INT_VALUE(args[3]); + } + } + + struct addrinfo *addr_list; + MP_THREAD_GIL_EXIT(); + int res = getaddrinfo(host, serv, &hints, &addr_list); + MP_THREAD_GIL_ENTER(); + + if (res != 0) { + // CPython: socket.gaierror +#if NO_NLR + return mp_raise_o( mp_obj_new_exception_msg_varg(&mp_type_OSError, "[addrinfo error %d]", res)); +#else + mp_raise_msg_varg( &mp_type_OSError, "[addrinfo error %d]", res); +#endif + } + assert(addr_list); + + mp_obj_t list = mp_obj_new_list(0, NULL); + for (struct addrinfo *addr = addr_list; addr; addr = addr->ai_next) { + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(addr->ai_family); + t->items[1] = MP_OBJ_NEW_SMALL_INT(addr->ai_socktype); + t->items[2] = MP_OBJ_NEW_SMALL_INT(addr->ai_protocol); + // "canonname will be a string representing the canonical name of the host + // if AI_CANONNAME is part of the flags argument; else canonname will be empty." ?? + if (addr->ai_canonname) { + t->items[3] = MP_OBJ_NEW_QSTR(qstr_from_str(addr->ai_canonname)); + } else { + t->items[3] = mp_const_none; + } + t->items[4] = mp_obj_new_bytearray(addr->ai_addrlen, addr->ai_addr); + mp_obj_list_append(list, MP_OBJ_FROM_PTR(t)); + } + freeaddrinfo(addr_list); + return list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_socket_getaddrinfo_obj, 2, 4, mod_socket_getaddrinfo); + +STATIC mp_obj_t mod_socket_sockaddr(mp_obj_t sockaddr_in) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(sockaddr_in, &bufinfo, MP_BUFFER_READ); + switch (((struct sockaddr *)bufinfo.buf)->sa_family) { + case AF_INET: { + struct sockaddr_in *sa = (struct sockaddr_in *)bufinfo.buf; + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(AF_INET); + t->items[1] = mp_obj_new_bytes((byte *)&sa->sin_addr, sizeof(sa->sin_addr)); + t->items[2] = MP_OBJ_NEW_SMALL_INT(ntohs(sa->sin_port)); + return MP_OBJ_FROM_PTR(t); + } + case AF_INET6: { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *)bufinfo.buf; + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(AF_INET6); + t->items[1] = mp_obj_new_bytes((byte *)&sa->sin6_addr, sizeof(sa->sin6_addr)); + t->items[2] = MP_OBJ_NEW_SMALL_INT(ntohs(sa->sin6_port)); + t->items[3] = MP_OBJ_NEW_SMALL_INT(ntohl(sa->sin6_flowinfo)); + t->items[4] = MP_OBJ_NEW_SMALL_INT(ntohl(sa->sin6_scope_id)); + return MP_OBJ_FROM_PTR(t); + } + default: { + struct sockaddr *sa = (struct sockaddr *)bufinfo.buf; + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(sa->sa_family); + t->items[1] = mp_obj_new_bytes((byte *)sa->sa_data, bufinfo.len - offsetof(struct sockaddr, sa_data)); + return MP_OBJ_FROM_PTR(t); + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_socket_sockaddr_obj, mod_socket_sockaddr); + +STATIC const mp_rom_map_elem_t mp_module_socket_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usocket) }, + { MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&mp_type_socket) }, + { MP_ROM_QSTR(MP_QSTR_getaddrinfo), MP_ROM_PTR(&mod_socket_getaddrinfo_obj) }, + { MP_ROM_QSTR(MP_QSTR_inet_pton), MP_ROM_PTR(&mod_socket_inet_pton_obj) }, + { MP_ROM_QSTR(MP_QSTR_inet_ntop), MP_ROM_PTR(&mod_socket_inet_ntop_obj) }, + { MP_ROM_QSTR(MP_QSTR_sockaddr), MP_ROM_PTR(&mod_socket_sockaddr_obj) }, + +#define C(name) { MP_ROM_QSTR(MP_QSTR_##name), MP_ROM_INT(name) } + C(AF_UNIX), + C(AF_INET), + C(AF_INET6), + C(SOCK_STREAM), + C(SOCK_DGRAM), + C(SOCK_RAW), + + C(MSG_DONTROUTE), + C(MSG_DONTWAIT), + + C(SOL_SOCKET), + C(SO_BROADCAST), + C(SO_ERROR), + C(SO_KEEPALIVE), + C(SO_LINGER), + C(SO_REUSEADDR), +#undef C +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_socket_globals, mp_module_socket_globals_table); + +const mp_obj_module_t mp_module_socket = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_socket_globals, +}; diff --git a/ports/wapy-unix/mpconfigport.h b/ports/wapy-unix/mpconfigport.h new file mode 100644 index 000000000..3a50fc3d1 --- /dev/null +++ b/ports/wapy-unix/mpconfigport.h @@ -0,0 +1,410 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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 NO_NLR +#define NO_NLR (1) +#endif + +#define MICROPY_PY_OS_DUPTERM (0) +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_EMIT_WASM (1) +#define MICROPY_PY_FSTRING (1) +#define MICROPY_PY_BUILTINS_NEXT2 (1) + + +#ifdef MICROPY_EMIT_NATIVE + #undef MICROPY_EMIT_NATIVE +#endif + +#ifdef MICROPY_ENABLE_SCHEDULER +#undef MICROPY_ENABLE_SCHEDULER +#endif +#define MICROPY_ENABLE_SCHEDULER (0) +#define MICROPY_PY_GENERATOR_PEND_THROW (0) +#define MICROPY_ENABLE_PYSTACK (1) + +// Options to control how MicroPython is built for this port, +// overriding defaults in py/mpconfig.h. + +// Variant-specific definitions. +#include "mpconfigvariant.h" + +// The minimal variant's config covers everything. +// If we're building the minimal variant, ignore the rest of this file. +#ifndef MICROPY_UNIX_MINIMAL + +#define MICROPY_ALLOC_PATH_MAX (PATH_MAX) +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#if !defined(MICROPY_EMIT_X64) && defined(__x86_64__) + #define MICROPY_EMIT_X64 (1) +#endif +#if !defined(MICROPY_EMIT_X86) && defined(__i386__) + #define MICROPY_EMIT_X86 (1) +#endif +#if !defined(MICROPY_EMIT_THUMB) && defined(__thumb2__) + #define MICROPY_EMIT_THUMB (1) + #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) +#endif +// Some compilers define __thumb2__ and __arm__ at the same time, let +// autodetected thumb2 emitter have priority. +#if !defined(MICROPY_EMIT_ARM) && defined(__arm__) && !defined(__thumb2__) + #define MICROPY_EMIT_ARM (1) +#endif +#define MICROPY_COMP_MODULE_CONST (1) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1) +#define MICROPY_COMP_RETURN_IF_EXPR (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_STACK_CHECK (1) +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#define MICROPY_MEM_STATS (1) +#define MICROPY_DEBUG_PRINTERS (1) +// Printing debug to stderr may give tests which +// check stdout a chance to pass, etc. +#define MICROPY_DEBUG_PRINTER (&mp_stderr_print) +#define MICROPY_READER_POSIX (1) + +#define MICROPY_USE_READLINE_HISTORY (1) +#define MICROPY_HELPER_REPL (1) +#define MICROPY_REPL_EMACS_KEYS (1) +#define MICROPY_REPL_AUTO_INDENT (1) +#define MICROPY_HELPER_LEXER_UNIX (1) +#define MICROPY_ENABLE_SOURCE_LINE (1) +#ifndef MICROPY_FLOAT_IMPL +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +#endif +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#ifndef MICROPY_STREAMS_NON_BLOCK +#define MICROPY_STREAMS_NON_BLOCK (1) +#endif +#define MICROPY_STREAMS_POSIX_API (1) +#define MICROPY_OPT_COMPUTED_GOTO (1) + +#ifndef MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE +#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#endif + +#define MICROPY_MODULE_WEAK_LINKS (1) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) +#define MICROPY_VFS_POSIX_FILE (1) +#if NO_NLR + #undef MICROPY_VFS_POSIX_FILE + #define MICROPY_VFS_POSIX_FILE (0) +#endif +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_PY_DESCRIPTORS (1) + +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#if MICROPY_PY_BUILTINS_STR_UNICODE + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (1) +#else + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (0) + #if NO_NLR + #error "unsupported native storage must be utf8" + #endif +#endif + +#define MICROPY_PY_BUILTINS_STR_CENTER (1) +#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_FROZENSET (1) +#define MICROPY_PY_BUILTINS_COMPILE (1) +#define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (1) +#define MICROPY_PY_BUILTINS_INPUT (1) +#define MICROPY_PY_BUILTINS_POW3 (1) +#define MICROPY_PY_BUILTINS_ROUND_INT (1) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) +#define MICROPY_PY_BUILTINS_SLICE_INDICES (1) +#define MICROPY_PY_SYS_EXIT (1) +#define MICROPY_PY_SYS_ATEXIT (1) +#if MICROPY_PY_SYS_SETTRACE +#define MICROPY_PERSISTENT_CODE_SAVE (1) +#define MICROPY_COMP_CONST (0) +#endif +#ifndef MICROPY_PY_SYS_PLATFORM +#if defined(__APPLE__) && defined(__MACH__) + #define MICROPY_PY_SYS_PLATFORM "darwin" +#else + #define MICROPY_PY_SYS_PLATFORM "linux" +#endif +#endif +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_STDFILES (1) +#define MICROPY_PY_SYS_EXC_INFO (1) +#define MICROPY_PY_COLLECTIONS_DEQUE (1) +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +#endif +#define MICROPY_PY_MATH_ISCLOSE (MICROPY_PY_MATH_SPECIAL_FUNCTIONS) +#define MICROPY_PY_CMATH (1) +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_GC_COLLECT_RETVAL (1) + +#ifndef MICROPY_STACKLESS +#define MICROPY_STACKLESS (0) +#define MICROPY_STACKLESS_STRICT (0) +#endif + +#define MICROPY_PY_OS_STATVFS (1) +#define MICROPY_PY_UTIME (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UZLIB (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_UHEAPQ (1) +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UHASHLIB (1) +#if MICROPY_PY_USSL +#define MICROPY_PY_UHASHLIB_MD5 (1) +#define MICROPY_PY_UHASHLIB_SHA1 (1) +#define MICROPY_PY_UCRYPTOLIB (1) +#endif +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UBINASCII_CRC32 (1) +#define MICROPY_PY_URANDOM (1) +#ifndef MICROPY_PY_USELECT_POSIX +#define MICROPY_PY_USELECT_POSIX (1) +#endif +#define MICROPY_PY_UWEBSOCKET (1) +#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_MACHINE_PULSE (1) +#define MICROPY_MACHINE_MEM_GET_READ_ADDR mod_machine_mem_get_addr +#define MICROPY_MACHINE_MEM_GET_WRITE_ADDR mod_machine_mem_get_addr + +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ + +// Define to MICROPY_ERROR_REPORTING_DETAILED to get function, etc. +// names in exception messages (may require more RAM). +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#define MICROPY_WARNINGS (1) +#define MICROPY_ERROR_PRINTER (&mp_stderr_print) +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) + +// VFS stat functions should return time values relative to 1970/1/1 +#define MICROPY_EPOCH_IS_1970 (1) + +extern const struct _mp_print_t mp_stderr_print; + +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) +// Fall back to setjmp() implementation for discovery of GC pointers in registers. +#define MICROPY_GCREGS_SETJMP (1) +#endif + +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (256) +#define MICROPY_KBD_EXCEPTION (1) +#define MICROPY_ASYNC_KBD_INTR (1) + +#define mp_type_fileio mp_type_vfs_posix_fileio +#define mp_type_textio mp_type_vfs_posix_textio + +extern const struct _mp_obj_module_t mp_module_machine; +extern const struct _mp_obj_module_t mp_module_os; +extern const struct _mp_obj_module_t mp_module_uos_vfs; +extern const struct _mp_obj_module_t mp_module_uselect; +extern const struct _mp_obj_module_t mp_module_time; +extern const struct _mp_obj_module_t mp_module_termios; +extern const struct _mp_obj_module_t mp_module_socket; +extern const struct _mp_obj_module_t mp_module_ffi; +extern const struct _mp_obj_module_t mp_module_jni; + +#if MICROPY_PY_UOS_VFS +#define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos_vfs) }, +#else +#define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_os) }, +#endif +#if MICROPY_PY_FFI +#define MICROPY_PY_FFI_DEF { MP_ROM_QSTR(MP_QSTR_ffi), MP_ROM_PTR(&mp_module_ffi) }, +#else +#define MICROPY_PY_FFI_DEF +#endif +#if MICROPY_PY_JNI +#define MICROPY_PY_JNI_DEF { MP_ROM_QSTR(MP_QSTR_jni), MP_ROM_PTR(&mp_module_jni) }, +#else +#define MICROPY_PY_JNI_DEF +#endif +#if MICROPY_PY_UTIME +#define MICROPY_PY_UTIME_DEF { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_time) }, +#else +#define MICROPY_PY_UTIME_DEF +#endif +#if MICROPY_PY_TERMIOS +#define MICROPY_PY_TERMIOS_DEF { MP_ROM_QSTR(MP_QSTR_termios), MP_ROM_PTR(&mp_module_termios) }, +#else +#define MICROPY_PY_TERMIOS_DEF +#endif +#if MICROPY_PY_SOCKET +#define MICROPY_PY_SOCKET_DEF { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_socket) }, +#else +#define MICROPY_PY_SOCKET_DEF +#endif +#if MICROPY_PY_USELECT_POSIX +#define MICROPY_PY_USELECT_DEF { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, +#else +#define MICROPY_PY_USELECT_DEF +#endif + +#define MICROPY_PORT_BUILTIN_MODULES \ + MICROPY_PY_FFI_DEF \ + MICROPY_PY_JNI_DEF \ + MICROPY_PY_UTIME_DEF \ + MICROPY_PY_SOCKET_DEF \ + { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&mp_module_machine) }, \ + MICROPY_PY_UOS_DEF \ + MICROPY_PY_USELECT_DEF \ + MICROPY_PY_TERMIOS_DEF \ + +// type definitions for the specific machine + +// For size_t and ssize_t +#include + +// assume that if we already defined the obj repr then we also defined types +#ifndef MICROPY_OBJ_REPR +#ifdef __LP64__ +typedef long mp_int_t; // must be pointer size +typedef unsigned long mp_uint_t; // must be pointer size +#else +// These are definitions for machines where sizeof(int) == sizeof(void*), +// regardless of actual size. +typedef int mp_int_t; // must be pointer size +typedef unsigned int mp_uint_t; // must be pointer size +#endif +#endif + +// Cannot include , as it may lead to symbol name clashes +#if _FILE_OFFSET_BITS == 64 && !defined(__LP64__) +typedef long long mp_off_t; +#else +typedef long mp_off_t; +#endif + +void mp_unix_alloc_exec(size_t min_size, void **ptr, size_t *size); +void mp_unix_free_exec(void *ptr, size_t size); +void mp_unix_mark_exec(void); +#define MP_PLAT_ALLOC_EXEC(min_size, ptr, size) mp_unix_alloc_exec(min_size, ptr, size) +#define MP_PLAT_FREE_EXEC(ptr, size) mp_unix_free_exec(ptr, size) +#ifndef MICROPY_FORCE_PLAT_ALLOC_EXEC +// Use MP_PLAT_ALLOC_EXEC for any executable memory allocation, including for FFI +// (overriding libffi own implementation) +#define MICROPY_FORCE_PLAT_ALLOC_EXEC (1) +#endif + +#ifdef __linux__ +// Can access physical memory using /dev/mem +#define MICROPY_PLAT_DEV_MEM (1) +#endif + +// Assume that select() call, interrupted with a signal, and erroring +// with EINTR, updates remaining timeout value. +#define MICROPY_SELECT_REMAINING_TIME (1) + +#ifdef __ANDROID__ +#include +#if __ANDROID_API__ < 4 +// Bionic libc in Android 1.5 misses these 2 functions +#define MP_NEED_LOG2 (1) +#define nan(x) NAN +#endif +#endif + +#define MICROPY_PORT_BUILTINS \ + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + +#define MP_STATE_PORT MP_STATE_VM + +#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 +#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 + +#define MICROPY_PORT_ROOT_POINTERS \ + const char *readline_hist[50]; \ + void *mmap_region_head; \ + void *PyOS_InputHook; \ + int coro_call_counter; \ + MICROPY_BLUETOOTH_ROOT_POINTERS \ + +// We need to provide a declaration/definition of alloca() +// unless support for it is disabled. +#if !defined(MICROPY_NO_ALLOCA) || MICROPY_NO_ALLOCA == 0 +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#endif + +// From "man readdir": "Under glibc, programs can check for the availability +// of the fields [in struct dirent] not defined in POSIX.1 by testing whether +// the macros [...], _DIRENT_HAVE_D_TYPE are defined." +// Other libc's don't define it, but proactively assume that dirent->d_type +// is available on a modern *nix system. +#ifndef _DIRENT_HAVE_D_TYPE +#define _DIRENT_HAVE_D_TYPE (1) +#endif +// This macro is not provided by glibc but we need it so ports that don't have +// dirent->d_ino can disable the use of this field. +#ifndef _DIRENT_HAVE_D_INO +#define _DIRENT_HAVE_D_INO (1) +#endif + +#ifndef __APPLE__ +// For debugging purposes, make printf() available to any source file. +#include +#endif + +#if MICROPY_PY_THREAD +#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 + +#define MICROPY_EVENT_POLL_HOOK mp_hal_delay_us(500); + +#include +#define MICROPY_UNIX_MACHINE_IDLE sched_yield(); + +#endif // MICROPY_UNIX_MINIMAL diff --git a/ports/wapy-unix/mpconfigport.mk b/ports/wapy-unix/mpconfigport.mk new file mode 100644 index 000000000..3a66d997b --- /dev/null +++ b/ports/wapy-unix/mpconfigport.mk @@ -0,0 +1,40 @@ +# Enable/disable modules and 3rd-party libs to be included in interpreter + +# Build 32-bit binaries on a 64-bit host +MICROPY_FORCE_32BIT = 0 + +# This variable can take the following values: +# 0 - no readline, just simple stdin input +# 1 - use MicroPython version of readline +MICROPY_USE_READLINE = 1 + +# btree module using Berkeley DB 1.xx +MICROPY_PY_BTREE = 1 + +# _thread module using pthreads +MICROPY_PY_THREAD = 1 + +# Subset of CPython termios module +MICROPY_PY_TERMIOS = 1 + +# Subset of CPython socket module +MICROPY_PY_SOCKET = 1 + +# ffi module requires libffi (libffi-dev Debian package) +MICROPY_PY_FFI = 1 + +# ussl module requires one of the TLS libraries below +MICROPY_PY_USSL = 1 +# axTLS has minimal size but implements only a subset of modern TLS +# functionality, so may have problems with some servers. +MICROPY_SSL_AXTLS = 1 +# mbedTLS is more up to date and complete implementation, but also +# more bloated. +MICROPY_SSL_MBEDTLS = 0 + +# jni module requires JVM/JNI +MICROPY_PY_JNI = 0 + +# Avoid using system libraries, use copies bundled with MicroPython +# as submodules (currently affects only libffi). +MICROPY_STANDALONE = 0 diff --git a/ports/wapy-unix/mphalport.h b/ports/wapy-unix/mphalport.h new file mode 100644 index 000000000..3bc442165 --- /dev/null +++ b/ports/wapy-unix/mphalport.h @@ -0,0 +1,109 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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 + +#ifndef CHAR_CTRL_C +#define CHAR_CTRL_C (3) +#endif + +void mp_hal_set_interrupt_char(int c); + +#define mp_hal_stdio_poll unused // this is not implemented, nor needed +void mp_hal_stdio_mode_raw(void); +void mp_hal_stdio_mode_orig(void); + +#if MICROPY_PY_BUILTINS_INPUT && MICROPY_USE_READLINE == 0 + +#include +#include "py/misc.h" +#include "input.h" +#define mp_hal_readline mp_hal_readline +static inline int mp_hal_readline(vstr_t *vstr, const char *p) { + char *line = prompt((char *)p); + vstr_add_str(vstr, line); + free(line); + return 0; +} + +#elif MICROPY_PY_BUILTINS_INPUT && MICROPY_USE_READLINE == 1 + +#include "py/misc.h" +#include "lib/mp-readline/readline.h" +// For built-in input() we need to wrap the standard readline() to enable raw mode +#define mp_hal_readline mp_hal_readline +static inline int mp_hal_readline(vstr_t *vstr, const char *p) { + mp_hal_stdio_mode_raw(); + int ret = readline(vstr, p); + mp_hal_stdio_mode_orig(); + return ret; +} + +#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); +} +#define mp_hal_ticks_cpu() 0 + +// This macro is used to implement PEP 475 to retry specified syscalls on EINTR +#define MP_HAL_RETRY_SYSCALL(ret, syscall, raise) { \ + for (;;) { \ + MP_THREAD_GIL_EXIT(); \ + ret = syscall; \ + MP_THREAD_GIL_ENTER(); \ + if (ret == -1) { \ + int err = errno; \ + if (err == EINTR) { \ + mp_handle_pending(true); \ + continue; \ + } \ + raise; \ + } \ + break; \ + } \ +} + +#define RAISE_ERRNO(err_flag, error_val) \ + { if (err_flag == -1) \ + { return mp_raise_OSError_o(error_val); } } + +#define RAISE_ERRNO_R(err_flag, error_val, ret_on_err) \ + { if (err_flag == -1) \ + { mp_raise_OSError_o(error_val); return ret_on_err; } } + +#if MICROPY_PY_BLUETOOTH +enum { + MP_HAL_MAC_BDADDR, +}; + +void mp_hal_get_mac(int idx, uint8_t buf[6]); +#endif diff --git a/ports/wapy-unix/mpthreadport.c b/ports/wapy-unix/mpthreadport.c new file mode 100644 index 000000000..f8369f6a6 --- /dev/null +++ b/ports/wapy-unix/mpthreadport.c @@ -0,0 +1,310 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + * + * 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 "py/runtime.h" +#include "py/mpthread.h" +#include "py/gc.h" + +#if MICROPY_PY_THREAD + +#include +#include +#include +#include + +#include "lib/utils/gchelper.h" + +// Some platforms don't have SIGRTMIN but if we do have it, use it to avoid +// potential conflict with other uses of the more commonly used SIGUSR1. +#ifdef SIGRTMIN +#define MP_THREAD_GC_SIGNAL (SIGRTMIN + 5) +#else +#define MP_THREAD_GC_SIGNAL (SIGUSR1) +#endif + +// This value seems to be about right for both 32-bit and 64-bit builds. +#define THREAD_STACK_OVERFLOW_MARGIN (8192) + +// this structure forms a linked list, one node per active thread +typedef struct _thread_t { + pthread_t id; // system id of thread + int ready; // whether the thread is ready and running + void *arg; // thread Python args, a GC root pointer + struct _thread_t *next; +} thread_t; + +STATIC pthread_key_t tls_key; + +// The mutex is used for any code in this port that needs to be thread safe. +// Specifically for thread management, access to the linked list is one example. +// But also, e.g. scheduler state. +STATIC pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; +STATIC thread_t *thread; + +// this is used to synchronise the signal handler of the thread +// it's needed because we can't use any pthread calls in a signal handler +#if defined(__APPLE__) +STATIC char thread_signal_done_name[25]; +STATIC sem_t *thread_signal_done_p; +#else +STATIC sem_t thread_signal_done; +#endif + +void mp_thread_unix_begin_atomic_section(void) { + pthread_mutex_lock(&thread_mutex); +} + +void mp_thread_unix_end_atomic_section(void) { + pthread_mutex_unlock(&thread_mutex); +} + +// this signal handler is used to scan the regs and stack of a thread +STATIC void mp_thread_gc(int signo, siginfo_t *info, void *context) { + (void)info; // unused + (void)context; // unused + if (signo == MP_THREAD_GC_SIGNAL) { + gc_helper_collect_regs_and_stack(); + // We have access to the context (regs, stack) of the thread but it seems + // that we don't need the extra information, enough is captured by the + // gc_collect_regs_and_stack function above + // gc_collect_root((void**)context, sizeof(ucontext_t) / sizeof(uintptr_t)); + #if MICROPY_ENABLE_PYSTACK + void **ptrs = (void **)(void *)MP_STATE_THREAD(pystack_start); + gc_collect_root(ptrs, (MP_STATE_THREAD(pystack_cur) - MP_STATE_THREAD(pystack_start)) / sizeof(void *)); + #endif + #if defined(__APPLE__) + sem_post(thread_signal_done_p); + #else + sem_post(&thread_signal_done); + #endif + } +} + +void mp_thread_init(void) { + pthread_key_create(&tls_key, NULL); + pthread_setspecific(tls_key, &mp_state_ctx.thread); + + // create first entry in linked list of all threads + thread = malloc(sizeof(thread_t)); + thread->id = pthread_self(); + thread->ready = 1; + thread->arg = NULL; + thread->next = NULL; + + #if defined(__APPLE__) + snprintf(thread_signal_done_name, sizeof(thread_signal_done_name), "micropython_sem_%d", (int)thread->id); + thread_signal_done_p = sem_open(thread_signal_done_name, O_CREAT | O_EXCL, 0666, 0); + #else + sem_init(&thread_signal_done, 0, 0); + #endif + + // enable signal handler for garbage collection + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = mp_thread_gc; + sigemptyset(&sa.sa_mask); + sigaction(MP_THREAD_GC_SIGNAL, &sa, NULL); +} + +void mp_thread_deinit(void) { + mp_thread_unix_begin_atomic_section(); + while (thread->next != NULL) { + thread_t *th = thread; + thread = thread->next; + pthread_cancel(th->id); + free(th); + } + mp_thread_unix_end_atomic_section(); + #if defined(__APPLE__) + sem_close(thread_signal_done_p); + sem_unlink(thread_signal_done_name); + #endif + assert(thread->id == pthread_self()); + free(thread); +} + +// This function scans all pointers that are external to the current thread. +// It does this by signalling all other threads and getting them to scan their +// own registers and stack. Note that there may still be some edge cases left +// with race conditions and root-pointer scanning: a given thread may manipulate +// the global root pointers (in mp_state_ctx) while another thread is doing a +// garbage collection and tracing these pointers. +void mp_thread_gc_others(void) { + mp_thread_unix_begin_atomic_section(); + for (thread_t *th = thread; th != NULL; th = th->next) { + gc_collect_root(&th->arg, 1); + if (th->id == pthread_self()) { + continue; + } + if (!th->ready) { + continue; + } + pthread_kill(th->id, MP_THREAD_GC_SIGNAL); + #if defined(__APPLE__) + sem_wait(thread_signal_done_p); + #else + sem_wait(&thread_signal_done); + #endif + } + mp_thread_unix_end_atomic_section(); +} + +mp_state_thread_t *mp_thread_get_state(void) { + return (mp_state_thread_t *)pthread_getspecific(tls_key); +} + +void mp_thread_set_state(mp_state_thread_t *state) { + pthread_setspecific(tls_key, state); +} + +void mp_thread_start(void) { + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + mp_thread_unix_begin_atomic_section(); + for (thread_t *th = thread; th != NULL; th = th->next) { + if (th->id == pthread_self()) { + th->ready = 1; + break; + } + } + mp_thread_unix_end_atomic_section(); +} + +void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { + // default stack size is 8k machine-words + if (*stack_size == 0) { + *stack_size = 8192 * (sizeof(void *)); + } + + // minimum stack size is set by pthreads + if (*stack_size < PTHREAD_STACK_MIN) { + *stack_size = PTHREAD_STACK_MIN; + } + + // ensure there is enough stack to include a stack-overflow margin + if (*stack_size < 2 * THREAD_STACK_OVERFLOW_MARGIN) { + *stack_size = 2 * THREAD_STACK_OVERFLOW_MARGIN; + } + + // set thread attributes + pthread_attr_t attr; + int ret = pthread_attr_init(&attr); + if (ret != 0) { + goto er; + } + ret = pthread_attr_setstacksize(&attr, *stack_size); + if (ret != 0) { + goto er; + } + + ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ret != 0) { + goto er; + } + + mp_thread_unix_begin_atomic_section(); + + // create thread + pthread_t id; + ret = pthread_create(&id, &attr, entry, arg); + if (ret != 0) { + mp_thread_unix_end_atomic_section(); + goto er; + } + + // adjust stack_size to provide room to recover from hitting the limit + *stack_size -= THREAD_STACK_OVERFLOW_MARGIN; + + // add thread to linked list of all threads + thread_t *th = malloc(sizeof(thread_t)); + th->id = id; + th->ready = 0; + th->arg = arg; + th->next = thread; + thread = th; + + mp_thread_unix_end_atomic_section(); + + return; + +er: +#if NO_NLR + mp_raise_o( mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(ret)) ); +#else + mp_raise_OSError(ret); +#endif + +} + +void mp_thread_finish(void) { + mp_thread_unix_begin_atomic_section(); + thread_t *prev = NULL; + for (thread_t *th = thread; th != NULL; th = th->next) { + if (th->id == pthread_self()) { + if (prev == NULL) { + thread = th->next; + } else { + prev->next = th->next; + } + free(th); + break; + } + prev = th; + } + mp_thread_unix_end_atomic_section(); +} + +void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { + pthread_mutex_init(mutex, NULL); +} + +int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { + int ret; + if (wait) { + ret = pthread_mutex_lock(mutex); + if (ret == 0) { + return 1; + } + } else { + ret = pthread_mutex_trylock(mutex); + if (ret == 0) { + return 1; + } else if (ret == EBUSY) { + return 0; + } + } + return -ret; +} + +void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { + pthread_mutex_unlock(mutex); + // TODO check return value +} + +#endif // MICROPY_PY_THREAD diff --git a/ports/wapy-unix/mpthreadport.h b/ports/wapy-unix/mpthreadport.h new file mode 100644 index 000000000..a7dbe08c4 --- /dev/null +++ b/ports/wapy-unix/mpthreadport.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + * + * 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 + +typedef pthread_mutex_t mp_thread_mutex_t; + +void mp_thread_init(void); +void mp_thread_deinit(void); +void mp_thread_gc_others(void); + +// Unix version of "enable/disable IRQs". +// Functions as a port-global lock for any code that must be serialised. +void mp_thread_unix_begin_atomic_section(void); +void mp_thread_unix_end_atomic_section(void); diff --git a/ports/wapy-unix/ports_unix_Makefile.diff b/ports/wapy-unix/ports_unix_Makefile.diff new file mode 100644 index 000000000..726b4fdc3 --- /dev/null +++ b/ports/wapy-unix/ports_unix_Makefile.diff @@ -0,0 +1,97 @@ +--- micropython-master-git/ports/unix/Makefile 2020-07-28 03:22:25.645101321 +0200 ++++ micropython-master-new/ports/unix/Makefile 2020-09-22 10:41:20.639978203 +0200 +@@ -47,10 +47,12 @@ + 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 +@@ -132,26 +134,60 @@ + 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 +-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 ++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 + +-ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) ++# 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 ++ + endif + + ifeq ($(MICROPY_PY_FFI),1) +@@ -198,7 +234,11 @@ + alloc.c \ + coverage.c \ + fatfs_port.c \ +- btstack_usb.c \ ++ mpbthciport.c \ ++ mpbtstackport_common.c \ ++ mpbtstackport_h4.c \ ++ mpbtstackport_usb.c \ ++ mpnimbleport.c \ + $(SRC_MOD) \ + $(wildcard $(VARIANT_DIR)/*.c) + diff --git a/ports/wapy-unix/qstrdefsport.h b/ports/wapy-unix/qstrdefsport.h new file mode 100644 index 000000000..8b827a8d6 --- /dev/null +++ b/ports/wapy-unix/qstrdefsport.h @@ -0,0 +1,27 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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. + */ + +// *FORMAT-OFF* diff --git a/ports/wapy-unix/unix_mphal.c b/ports/wapy-unix/unix_mphal.c new file mode 100644 index 000000000..dff3b869a --- /dev/null +++ b/ports/wapy-unix/unix_mphal.c @@ -0,0 +1,225 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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 +#include +#include + +#include "py/mphal.h" +#include "py/mpthread.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#ifndef _WIN32 +#include + +STATIC void sighandler(int signum) { + if (signum == SIGINT) { + #if MICROPY_ASYNC_KBD_INTR + #if MICROPY_PY_THREAD_GIL + // Since signals can occur at any time, we may not be holding the GIL when + // this callback is called, so it is not safe to raise an exception here + #error "MICROPY_ASYNC_KBD_INTR and MICROPY_PY_THREAD_GIL are not compatible" + #endif + mp_obj_exception_clear_traceback(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); + sigset_t mask; + sigemptyset(&mask); + // On entry to handler, its signal is blocked, and unblocked on + // normal exit. As we instead perform longjmp, unblock it manually. + sigprocmask(SIG_SETMASK, &mask, NULL); +#if NO_NLR + mp_raise_o(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); +#else + nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); +#endif + #else + if (MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))) { + // this is the second time we are called, so die straight away + exit(1); + } + mp_keyboard_interrupt(); + #endif + } +} +#endif + +void mp_hal_set_interrupt_char(int c) { + // configure terminal settings to (not) let ctrl-C through + if (c == CHAR_CTRL_C) { + #ifndef _WIN32 + // enable signal handler + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = sighandler; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + #endif + } else { + #ifndef _WIN32 + // disable signal handler + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + #endif + } +} + +#if MICROPY_USE_READLINE == 1 + +#include + +static struct termios orig_termios; + +void mp_hal_stdio_mode_raw(void) { + // save and set terminal settings + tcgetattr(0, &orig_termios); + static struct termios termios; + termios = orig_termios; + termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + termios.c_cflag = (termios.c_cflag & ~(CSIZE | PARENB)) | CS8; + termios.c_lflag = 0; + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 0; + tcsetattr(0, TCSAFLUSH, &termios); +} + +void mp_hal_stdio_mode_orig(void) { + // restore terminal settings + tcsetattr(0, TCSAFLUSH, &orig_termios); +} + +#endif + +#if MICROPY_PY_OS_DUPTERM +static int call_dupterm_read(size_t idx) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t read_m[3]; + mp_load_method(MP_STATE_VM(dupterm_objs[idx]), MP_QSTR_read, read_m); + read_m[2] = MP_OBJ_NEW_SMALL_INT(1); + mp_obj_t res = mp_call_method_n_kw(1, 0, read_m); + if (res == mp_const_none) { + return -2; + } + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(res, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len == 0) { + mp_printf(&mp_plat_print, "dupterm: EOF received, deactivating\n"); + MP_STATE_VM(dupterm_objs[idx]) = MP_OBJ_NULL; + return -1; + } + nlr_pop(); + return *(byte *)bufinfo.buf; + } else { + // Temporarily disable dupterm to avoid infinite recursion + mp_obj_t save_term = MP_STATE_VM(dupterm_objs[idx]); + MP_STATE_VM(dupterm_objs[idx]) = NULL; + mp_printf(&mp_plat_print, "dupterm: "); + mp_obj_print_exception(&mp_plat_print, nlr.ret_val); + MP_STATE_VM(dupterm_objs[idx]) = save_term; + } + + return -1; +} +#endif + +int mp_hal_stdin_rx_chr(void) { + #if MICROPY_PY_OS_DUPTERM + // TODO only support dupterm one slot at the moment + if (MP_STATE_VM(dupterm_objs[0]) != MP_OBJ_NULL) { + int c; + do { + c = call_dupterm_read(0); + } while (c == -2); + if (c == -1) { + goto main_term; + } + if (c == '\n') { + c = '\r'; + } + return c; + } +main_term:; + #endif + + unsigned char c; + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, read(STDIN_FILENO, &c, 1), {}); + if (ret == 0) { + c = 4; // EOF, ctrl-D + } else if (c == '\n') { + c = '\r'; + } + return c; +} + +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + ssize_t ret; + MP_HAL_RETRY_SYSCALL(ret, write(STDOUT_FILENO, str, len), {}); + mp_uos_dupterm_tx_strn(str, len); +} + +// cooked is same as uncooked because the terminal does some postprocessing +void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) { + mp_hal_stdout_tx_strn(str, len); +} + +void mp_hal_stdout_tx_str(const char *str) { + mp_hal_stdout_tx_strn(str, strlen(str)); +} + +mp_uint_t mp_hal_ticks_ms(void) { + #if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0) && defined(_POSIX_MONOTONIC_CLOCK) + struct timespec tv; + clock_gettime(CLOCK_MONOTONIC, &tv); + return tv.tv_sec * 1000 + tv.tv_nsec / 1000000; + #else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; + #endif +} + +mp_uint_t mp_hal_ticks_us(void) { + #if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0) && defined(_POSIX_MONOTONIC_CLOCK) + struct timespec tv; + clock_gettime(CLOCK_MONOTONIC, &tv); + return tv.tv_sec * 1000000 + tv.tv_nsec / 1000; + #else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000 + tv.tv_usec; + #endif +} + +uint64_t mp_hal_time_ns(void) { + time_t now = time(NULL); + return (uint64_t)now * 1000000000ULL; +} diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy1.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy1.py new file mode 100644 index 000000000..8ad0f1573 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy1.py @@ -0,0 +1 @@ +print('frzmpy1') diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy2.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy2.py new file mode 100644 index 000000000..1ad930db2 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy2.py @@ -0,0 +1 @@ +raise ZeroDivisionError diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py new file mode 100644 index 000000000..8c023afeb --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzmpy_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py new file mode 100644 index 000000000..a66b505bf --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzmpy_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzmpy_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/variants/coverage/frzmpy/frzqstr.py b/ports/wapy-unix/variants/coverage/frzmpy/frzqstr.py new file mode 100644 index 000000000..051f2a9c1 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzmpy/frzqstr.py @@ -0,0 +1,3 @@ +# Checks for regression on MP_QSTR_NULL +def returns_NULL(): + return "NULL" diff --git a/ports/wapy-unix/variants/coverage/frzstr/frzstr1.py b/ports/wapy-unix/variants/coverage/frzstr/frzstr1.py new file mode 100644 index 000000000..6e88ac38d --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzstr/frzstr1.py @@ -0,0 +1 @@ +print('frzstr1') diff --git a/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py new file mode 100644 index 000000000..1d1df9417 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg1/__init__.py @@ -0,0 +1,3 @@ +# test frozen package with __init__.py +print('frzstr_pkg1.__init__') +x = 1 diff --git a/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg2/mod.py b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg2/mod.py new file mode 100644 index 000000000..bafb5978b --- /dev/null +++ b/ports/wapy-unix/variants/coverage/frzstr/frzstr_pkg2/mod.py @@ -0,0 +1,4 @@ +# test frozen package without __init__.py +print('frzstr_pkg2.mod') +class Foo: + x = 1 diff --git a/ports/wapy-unix/variants/coverage/manifest.py b/ports/wapy-unix/variants/coverage/manifest.py new file mode 100644 index 000000000..611105088 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/manifest.py @@ -0,0 +1,2 @@ +freeze_as_str("frzstr") +freeze_as_mpy("frzmpy") diff --git a/ports/wapy-unix/variants/coverage/mpconfigvariant.h b/ports/wapy-unix/variants/coverage/mpconfigvariant.h new file mode 100644 index 000000000..3de529432 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/mpconfigvariant.h @@ -0,0 +1,69 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-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. + */ + +// This config enables almost all possible features such that it can be used +// for coverage testing. + +#define MICROPY_VFS (1) +#define MICROPY_PY_UOS_VFS (1) + +#define MICROPY_OPT_MATH_FACTORIAL (1) +#define MICROPY_FLOAT_HIGH_QUALITY_HASH (1) +#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_READER_VFS (1) +#define MICROPY_REPL_EMACS_WORDS_MOVE (1) +#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) +#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) +#define MICROPY_PY_BUILTINS_RANGE_BINOP (1) +#define MICROPY_PY_BUILTINS_HELP (1) +#define MICROPY_PY_BUILTINS_HELP_MODULES (1) +#define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_MATH_FACTORIAL (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_IO_BUFFEREDWRITER (1) +#define MICROPY_PY_IO_RESOURCE_STREAM (1) +#define MICROPY_PY_UASYNCIO (1) +#define MICROPY_PY_URE_DEBUG (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_VFS_POSIX (1) +#define MICROPY_PY_FRAMEBUF (1) +#define MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT (1) +#define MICROPY_PY_UCRYPTOLIB (1) +#define MICROPY_PY_UCRYPTOLIB_CTR (1) +#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (1) + +// use vfs's functions for import stat and builtin open +#define mp_import_stat mp_vfs_import_stat +#define mp_builtin_open mp_vfs_open +#define mp_builtin_open_obj mp_vfs_open_obj diff --git a/ports/wapy-unix/variants/coverage/mpconfigvariant.mk b/ports/wapy-unix/variants/coverage/mpconfigvariant.mk new file mode 100644 index 000000000..f11d0b0d2 --- /dev/null +++ b/ports/wapy-unix/variants/coverage/mpconfigvariant.mk @@ -0,0 +1,19 @@ +PROG ?= micropython-coverage + +# Disable optimisations and enable assert() on coverage builds. +DEBUG ?= 1 + +CFLAGS += \ + -fprofile-arcs -ftest-coverage \ + -Wformat -Wmissing-declarations -Wmissing-prototypes \ + -Wold-style-definition -Wpointer-arith -Wshadow -Wuninitialized -Wunused-parameter \ + -DMICROPY_UNIX_COVERAGE + +LDFLAGS += -fprofile-arcs -ftest-coverage + +FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py + +MICROPY_ROM_TEXT_COMPRESSION = 1 +MICROPY_VFS_FAT = 1 +MICROPY_VFS_LFS1 = 1 +MICROPY_VFS_LFS2 = 1 diff --git a/ports/wapy-unix/variants/dev/manifest.py b/ports/wapy-unix/variants/dev/manifest.py new file mode 100644 index 000000000..92a681116 --- /dev/null +++ b/ports/wapy-unix/variants/dev/manifest.py @@ -0,0 +1,3 @@ +include("$(PORT_DIR)/variants/manifest.py") + +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") diff --git a/ports/wapy-unix/variants/dev/mpconfigvariant.h b/ports/wapy-unix/variants/dev/mpconfigvariant.h new file mode 100644 index 000000000..7c3e84cc4 --- /dev/null +++ b/ports/wapy-unix/variants/dev/mpconfigvariant.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#define MICROPY_READER_VFS (1) +#define MICROPY_REPL_EMACS_WORDS_MOVE (1) +#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1) +#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_VFS (1) +#define MICROPY_VFS_POSIX (1) + +#define MICROPY_PY_SYS_SETTRACE (1) +#define MICROPY_PY_UOS_VFS (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) + +#ifndef MICROPY_PY_UASYNCIO +#define MICROPY_PY_UASYNCIO (1) +#endif + +// Use vfs's functions for import stat and builtin open. +#define mp_import_stat mp_vfs_import_stat +#define mp_builtin_open mp_vfs_open +#define mp_builtin_open_obj mp_vfs_open_obj diff --git a/ports/wapy-unix/variants/dev/mpconfigvariant.mk b/ports/wapy-unix/variants/dev/mpconfigvariant.mk new file mode 100644 index 000000000..91bd28da9 --- /dev/null +++ b/ports/wapy-unix/variants/dev/mpconfigvariant.mk @@ -0,0 +1,10 @@ +PROG ?= micropython-dev + +FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py + +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/wapy-unix/variants/fast/mpconfigvariant.h b/ports/wapy-unix/variants/fast/mpconfigvariant.h new file mode 100644 index 000000000..8a531b056 --- /dev/null +++ b/ports/wapy-unix/variants/fast/mpconfigvariant.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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. + */ + +// This config file is intended to configure artificially fast uPy build for +// synthetic benchmarking, at the expense of features supported and memory +// usage. This config is not intended to be used in production. + +#define MICROPY_PY___FILE__ (0) +// 91 is a magic number proposed by @dpgeorge, which make pystone run ~ at tie +// with CPython 3.4. +#define MICROPY_MODULE_DICT_SIZE (91) diff --git a/ports/wapy-unix/variants/fast/mpconfigvariant.mk b/ports/wapy-unix/variants/fast/mpconfigvariant.mk new file mode 100644 index 000000000..595e57564 --- /dev/null +++ b/ports/wapy-unix/variants/fast/mpconfigvariant.mk @@ -0,0 +1,7 @@ +# build synthetically fast interpreter for benchmarking + +COPT += -fno-crossjumping -O2 + +PROG = micropython-fast + +FROZEN_MANIFEST = diff --git a/ports/wapy-unix/variants/freedos/mpconfigvariant.h b/ports/wapy-unix/variants/freedos/mpconfigvariant.h new file mode 100644 index 000000000..562c783ca --- /dev/null +++ b/ports/wapy-unix/variants/freedos/mpconfigvariant.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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 to control how MicroPython is built + +#define MICROPY_PY_USELECT_POSIX (0) + +#define MICROPY_STREAMS_NON_BLOCK (0) + +#define MICROPY_PY_SYS_PLATFORM "freedos" + +// djgpp dirent struct does not have d_ino field +#undef _DIRENT_HAVE_D_INO + +#define MICROPY_USE_INTERNAL_ERRNO (1) diff --git a/ports/wapy-unix/variants/freedos/mpconfigvariant.mk b/ports/wapy-unix/variants/freedos/mpconfigvariant.mk new file mode 100644 index 000000000..a30db3e0c --- /dev/null +++ b/ports/wapy-unix/variants/freedos/mpconfigvariant.mk @@ -0,0 +1,20 @@ +CC = i586-pc-msdosdjgpp-gcc + +STRIP = i586-pc-msdosdjgpp-strip + +SIZE = i586-pc-msdosdjgpp-size + +CFLAGS += \ + -DMICROPY_NLR_SETJMP \ + -Dtgamma=gamma \ + -DMICROPY_EMIT_X86=0 \ + -DMICROPY_NO_ALLOCA=1 \ + +PROG = micropython-freedos + +MICROPY_PY_SOCKET = 0 +MICROPY_PY_FFI = 0 +MICROPY_PY_JNI = 0 +MICROPY_PY_BTREE = 0 +MICROPY_PY_THREAD = 0 +MICROPY_PY_USSL = 0 diff --git a/ports/wapy-unix/variants/manifest.py b/ports/wapy-unix/variants/manifest.py new file mode 100644 index 000000000..666b4c0ab --- /dev/null +++ b/ports/wapy-unix/variants/manifest.py @@ -0,0 +1,2 @@ +freeze_as_mpy('$(MPY_DIR)/tools', 'upip.py') +freeze_as_mpy('$(MPY_DIR)/tools', 'upip_utarfile.py', opt=3) diff --git a/ports/wapy-unix/variants/minimal/mpconfigvariant.h b/ports/wapy-unix/variants/minimal/mpconfigvariant.h new file mode 100644 index 000000000..e87b5d8ec --- /dev/null +++ b/ports/wapy-unix/variants/minimal/mpconfigvariant.h @@ -0,0 +1,142 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2015 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 to control how MicroPython is built + +// Prevent the rest of the default mpconfigport.h being used. +#define MICROPY_UNIX_MINIMAL (1) + +#define MICROPY_ALLOC_QSTR_CHUNK_INIT (64) +#define MICROPY_ALLOC_PARSE_RULE_INIT (8) +#define MICROPY_ALLOC_PARSE_RULE_INC (8) +#define MICROPY_ALLOC_PARSE_RESULT_INIT (8) +#define MICROPY_ALLOC_PARSE_RESULT_INC (8) +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) +#define MICROPY_ALLOC_PATH_MAX (PATH_MAX) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_GC_ALLOC_THRESHOLD (0) +#define MICROPY_ENABLE_FINALISER (0) +#define MICROPY_STACK_CHECK (0) +#define MICROPY_COMP_CONST (0) +#define MICROPY_MEM_STATS (0) +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_READER_POSIX (1) +#define MICROPY_HELPER_REPL (1) +#define MICROPY_HELPER_LEXER_UNIX (1) +#define MICROPY_ENABLE_SOURCE_LINE (0) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) +#define MICROPY_WARNINGS (0) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (0) +#define MICROPY_KBD_EXCEPTION (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_NONE) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_NONE) +#define MICROPY_STREAMS_NON_BLOCK (0) +#define MICROPY_OPT_COMPUTED_GOTO (0) +#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#define MICROPY_CAN_OVERRIDE_BUILTINS (0) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) +#define MICROPY_CPYTHON_COMPAT (0) +#define MICROPY_PY_BUILTINS_BYTEARRAY (0) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (0) +#define MICROPY_PY_BUILTINS_COMPILE (0) +#define MICROPY_PY_BUILTINS_ENUMERATE (0) +#define MICROPY_PY_BUILTINS_FILTER (0) +#define MICROPY_PY_BUILTINS_FROZENSET (0) +#define MICROPY_PY_BUILTINS_REVERSED (0) +#define MICROPY_PY_BUILTINS_SET (0) +#define MICROPY_PY_BUILTINS_SLICE (0) +#define MICROPY_PY_BUILTINS_STR_COUNT (0) +#define MICROPY_PY_BUILTINS_STR_OP_MODULO (0) +#define MICROPY_PY_BUILTINS_STR_UNICODE (0) +#define MICROPY_PY_BUILTINS_PROPERTY (0) +#define MICROPY_PY_BUILTINS_MIN_MAX (0) +#define MICROPY_PY___FILE__ (0) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (0) +#define MICROPY_PY_GC (0) +#define MICROPY_PY_GC_COLLECT_RETVAL (0) +#define MICROPY_PY_ARRAY (0) +#define MICROPY_PY_COLLECTIONS (0) +#define MICROPY_PY_MATH (0) +#define MICROPY_PY_CMATH (0) +#define MICROPY_PY_IO (0) +#define MICROPY_PY_IO_FILEIO (0) +#define MICROPY_PY_STRUCT (0) +#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS_EXIT (0) +#define MICROPY_PY_SYS_PLATFORM "linux" +#define MICROPY_PY_SYS_MAXSIZE (0) +#define MICROPY_PY_SYS_STDFILES (0) +#define MICROPY_PY_CMATH (0) +#define MICROPY_PY_UCTYPES (0) +#define MICROPY_PY_UTIME (0) +#define MICROPY_PY_UZLIB (0) +#define MICROPY_PY_UJSON (0) +#define MICROPY_PY_URE (0) +#define MICROPY_PY_UHEAPQ (0) +#define MICROPY_PY_UHASHLIB (0) +#define MICROPY_PY_UBINASCII (0) + +extern const struct _mp_obj_module_t mp_module_os; + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_os) }, \ + +#define MICROPY_PORT_ROOT_POINTERS \ + +////////////////////////////////////////// +// Do not change anything beyond this line +////////////////////////////////////////// + +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) +// Fall back to setjmp() implementation for discovery of GC pointers in registers. +#define MICROPY_GCREGS_SETJMP (1) +#endif + +// type definitions for the specific machine + +#ifdef __LP64__ +typedef long mp_int_t; // must be pointer size +typedef unsigned long mp_uint_t; // must be pointer size +#else +// These are definitions for machines where sizeof(int) == sizeof(void*), +// regardless for actual size. +typedef int mp_int_t; // must be pointer size +typedef unsigned int mp_uint_t; // must be pointer size +#endif + +// Cannot include , as it may lead to symbol name clashes +#if _FILE_OFFSET_BITS == 64 && !defined(__LP64__) +typedef long long mp_off_t; +#else +typedef long mp_off_t; +#endif + +// We need to provide a declaration/definition of alloca() +#ifdef __FreeBSD__ +#include +#else +#include +#endif diff --git a/ports/wapy-unix/variants/minimal/mpconfigvariant.mk b/ports/wapy-unix/variants/minimal/mpconfigvariant.mk new file mode 100644 index 000000000..ec3b21c0b --- /dev/null +++ b/ports/wapy-unix/variants/minimal/mpconfigvariant.mk @@ -0,0 +1,13 @@ +# build a minimal interpreter +PROG = micropython-minimal + +FROZEN_MANIFEST = + +MICROPY_ROM_TEXT_COMPRESSION = 1 +MICROPY_PY_BTREE = 0 +MICROPY_PY_FFI = 0 +MICROPY_PY_SOCKET = 0 +MICROPY_PY_THREAD = 0 +MICROPY_PY_TERMIOS = 0 +MICROPY_PY_USSL = 0 +MICROPY_USE_READLINE = 0 diff --git a/ports/wapy-unix/variants/nanbox/mpconfigvariant.h b/ports/wapy-unix/variants/nanbox/mpconfigvariant.h new file mode 100644 index 000000000..f827158fb --- /dev/null +++ b/ports/wapy-unix/variants/nanbox/mpconfigvariant.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +// This config is mostly used to ensure that the nan-boxing object model +// continues to build (i.e. catches usage of mp_obj_t that don't work with +// this representation). + +// select nan-boxing object model +#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_D) + +// native emitters don't work with nan-boxing +#define MICROPY_EMIT_X86 (0) +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_ARM (0) + +#include + +typedef int64_t mp_int_t; +typedef uint64_t mp_uint_t; +#define UINT_FMT "%llu" +#define INT_FMT "%lld" diff --git a/ports/wapy-unix/variants/nanbox/mpconfigvariant.mk b/ports/wapy-unix/variants/nanbox/mpconfigvariant.mk new file mode 100644 index 000000000..9752b922c --- /dev/null +++ b/ports/wapy-unix/variants/nanbox/mpconfigvariant.mk @@ -0,0 +1,4 @@ +# build interpreter with nan-boxing as object model (object repr D) +PROG = micropython-nanbox + +MICROPY_FORCE_32BIT = 1 diff --git a/ports/wapy-unix/variants/standard/mpconfigvariant.h b/ports/wapy-unix/variants/standard/mpconfigvariant.h new file mode 100644 index 000000000..79b8fe2a3 --- /dev/null +++ b/ports/wapy-unix/variants/standard/mpconfigvariant.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + diff --git a/ports/wapy-unix/variants/standard/mpconfigvariant.mk b/ports/wapy-unix/variants/standard/mpconfigvariant.mk new file mode 100644 index 000000000..cf3efab8a --- /dev/null +++ b/ports/wapy-unix/variants/standard/mpconfigvariant.mk @@ -0,0 +1,3 @@ +# This is the default variant when you `make` the Unix port. + +PROG ?= micropython diff --git a/ports/wapy-wasi/Makefile b/ports/wapy-wasi/Makefile new file mode 100644 index 000000000..52c413b57 --- /dev/null +++ b/ports/wapy-wasi/Makefile @@ -0,0 +1,191 @@ +#PORTS=$(EM_CACHE)/asmjs/ports-builds +#FROZEN_MPY_DIR ?= modules +#FROZEN_DIR ?= flash + + +BASENAME=wapy +PROG=$(BASENAME).wasm +LIBPYTHON = lib$(BASENAME).a +CROSS = 0 + +# clang has slightly different options to GCC +CLANG = 1 + +# compiler settings +ifdef CWARN +else +CFLAGS += $(CWARN) -Wall -Werror +endif + +CFLAGS += -Wpointer-arith -Wuninitialized -ferror-limit=2 + + +include ../../py/mkenv.mk + +#EM_CACHE ?= $(HOME)/.emscripten_cache + +# qstr definitions (must come before including py.mk) +QSTR_DEFS = qstrdefsport.h + + +CX = --sysroot=/data/git/wapy-pack/wasi-sdk/share/wasi-sysroot --target=wasm32-unknow-wasi -D__WASI__=1 -DNO_NLR=1 + +CC ?= clang +CC += $(CX) +CPP=clang $(CX) -E -D__CPP__ -D__WASI__=1 -DNO_NLR=1 + +CPPFLAGS += $(CX) $(INC) $(WARN) -ansi -std=gnu11 -Wno-unused-variable + + +# include py core make definitions +include ../../py/py.mk + + +SIZE = echo + +INC += -I. +INC += -I../.. +INC += -I$(BUILD) + +#stubs +INC += -I../wapy -I../wapy/stubs + + +#LD_SHARED = -Wl,-map,$@.map -Wl,-dead_strip -Wl,-no_pie +LD_SHARED += -fno-exceptions -fno-rtti + + + +ifneq ($(FROZEN_DIR),) +# To use frozen source modules, put your .py files in a subdirectory (eg scripts/) +# and then invoke make with FROZEN_DIR=scripts (be sure to build from scratch). +CPPFLAGS += -DMICROPY_MODULE_FROZEN_STR +endif + +ifneq ($(FROZEN_MPY_DIR),) +# To use frozen bytecode, put your .py files in a subdirectory (eg frozen/) and +# then invoke make with FROZEN_MPY_DIR=frozen (be sure to build from scratch). +# //for qstr.c + +CPPFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool +CPPFLAGS += -DMICROPY_MODULE_FROZEN_MPY +endif + +# //for build/genhdr/qstr.i.last +# QSTR_GEN_EXTRA_CFLAGS=-DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool + +MPY_CROSS_FLAGS += -mcache-lookup-bc + +include ../wapy/wapy.mk + + +SRC_C = \ + ../wapy/core/vfs.c \ + ../wapy/core/ringbuf_b.c \ + ../wapy/core/ringbuf_o.c \ + ../wapy/core/file.wapy.c \ + mod/modos.c \ + mod/modtime.c \ + mod/moduos_vfs.c \ + +ifdef FFI +# optionnal experimental FFI +SRC_C+= \ + mod/modffi.c \ + mod/ffi/ffi.c \ + mod/ffi/types.c \ + mod/ffi/prep_cif.c +endif + +LIB_SRC_C= $(addprefix lib/, \ + utils/stdout_helpers.c \ + utils/pyexec.c \ + utils/interrupt_char.c \ + mp-readline/readline.c \ + ) + +# now using user mod dir, SRC_MOD use USER_C_MODULES + +ifdef LVGL + LIB_SRC_C_EXTRA += $(addprefix lib/,\ + lv_bindings/driver/SDL/SDL_monitor.c \ + lv_bindings/driver/SDL/SDL_mouse.c \ + lv_bindings/driver/SDL/modSDL.c \ + timeutils/timeutils.c \ + ) + CPPFLAGS += -Wno-unused-function -Wno-for-loop-analysis +endif + + + +# List of sources for qstr extraction +SRC_QSTR += $(SRC_C) qstr/objtype.c qstr/modbuiltins.c +SRC_QSTR += $(LIB_SRC_C) + + +# Append any auto-generated sources that are needed by sources listed in +# SRC_QSTR +SRC_QSTR_AUTO_DEPS += SRC_QSTR + +OBJ = $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C_EXTRA:.c=.o)) + + +CPPFLAGS += -DNO_NLR=$(NO_NLR) +CFLAGS += $(CPPFLAGS) $(CFLAGS_EXTRA) + + +all: $(PROG) + +include ../../py/mkrules.mk + + +# one day, maybe go via a *embeddable* static lib first ? +LIBPYTHON = lib$(BASENAME)$(TARGET).a + +#force preprocessor env to be created before qstr extraction +ifdef EMSCRIPTEN +$(BUILD)/clang_predefs.h: + $(Q)mkdir -p $(dir $@) + $(Q)emcc $(CFLAGS) $(JSFLAGS) -E -x c /dev/null -dM > $@ + +# Create `clang_predefs.h` as soon as possible, using a Makefile trick + +Makefile: $(BUILD)/clang_predefs.h +endif + + + +clean: + $(RM) -rf $(BUILD) $(CLEAN_EXTRA) $(LIBPYTHON) + $(shell rm $(BASENAME)/$(BASENAME).* || echo echo test data cleaned up) + + +COPT += $(JSFLAGS) $(WASM_FLAGS) + + +lib-static: $(OBJ) + $(ECHO) "Linking static $(LIBPYTHON)" + $(Q)$(AR) rcs $(LIBPYTHON) $(OBJ) + +lib-shared: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).wasm" + $(Q)$(LD) $(LD_SHARED) $(LD_LIB) -o lib$(BASENAME)$(TARGET).wasm $(OBJ) -ldl -lc + +libs: lib-static lib-shared + +# ============ static + pic was SERIOUSLY BROKEN until clang 11 ========== +# but this is the good one +LD_PROG += $(CFLAGS) $(WASM_FLAGS) +LD_LIB += $(WASM_FLAGS) +CFLAGS += -fPIC + +$(PROG): lib-static + $(ECHO) "Building static executable $@" + $(Q)$(CC) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) \ + -o $@ main.c lib$(BASENAME)$(TARGET).a -ldl -lm $(BLOBS) $(PF) + $(shell mv $(BASENAME).* $(BASENAME)/) + diff --git a/ports/wapy-wasi/api b/ports/wapy-wasi/api new file mode 120000 index 000000000..caac86790 --- /dev/null +++ b/ports/wapy-wasi/api @@ -0,0 +1 @@ +../wapy-wasm/api \ No newline at end of file diff --git a/ports/wapy-wasi/bc_as_integers.h b/ports/wapy-wasi/bc_as_integers.h new file mode 120000 index 000000000..f1ab533d9 --- /dev/null +++ b/ports/wapy-wasi/bc_as_integers.h @@ -0,0 +1 @@ +../wapy-wasm/bc_as_integers.h \ No newline at end of file diff --git a/ports/wapy-wasi/dev_conf.h b/ports/wapy-wasi/dev_conf.h new file mode 120000 index 000000000..11f5bcb51 --- /dev/null +++ b/ports/wapy-wasi/dev_conf.h @@ -0,0 +1 @@ +../wapy-wasm/dev_conf.h \ No newline at end of file diff --git a/ports/wapy-wasi/file.c.base b/ports/wapy-wasi/file.c.base new file mode 100644 index 000000000..7e787c06d --- /dev/null +++ b/ports/wapy-wasi/file.c.base @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +//#include + +#include "py/qstr.h" + + +#include // needed because mp_is_nonblocking_error uses system error codes +#include +#include +#include +#include +#include "py/mphal.h" +#include "py/mpprint.h" +#include "py/obj.h" +#include "py/objint.h" +#include "py/runtime.h" +#include "py/formatfloat.h" +#include "py/mpconfig.h" +#include "py/mpstate.h" +#include "py/misc.h" +#include "py/gc.h" +#include "py/unicode.h" +#include "py/mpz.h" +#include "py/mperrno.h" +#include "py/mpthread.h" +#include "py/reader.h" +#include "py/lexer.h" +#include "py/parse.h" +#include "py/parsenum.h" +#include "py/objstr.h" +#include "py/builtin.h" +//#include "py/grammar.h" +#include "py/scope.h" +#include "py/emit.h" +#include "py/compile.h" +#include "py/asmbase.h" +#include "py/persistentcode.h" +#include "py/bc0.h" +#include "py/asmx64.h" +#include "py/emitnative.c" +#include "py/asmx86.h" +#include "py/nativeglue.h" +#include "py/asmthumb.h" +#include "py/asmarm.h" +#include "py/asmxtensa.h" +#include "py/parsenumbase.h" +#include "py/smallint.h" +#include "py/emitglue.h" +#include "py/runtime0.h" +#include "py/bc.h" +#include "py/profile.h" +#include "py/objtuple.h" +#include "py/objlist.h" +#include "py/objtype.h" +#include "py/objmodule.h" +#include "py/objgenerator.h" +#include "py/stackctrl.h" +#include "upython.h" +#include "py/pairheap.h" +#include "py/ringbuf.h" +#include "py/binary.h" +#include "py/objarray.h" + +#include "py/objfun.h" +#include "genhdr/moduledefs.h" +#include "py/objnamedtuple.h" +#include "py/objstringio.h" +#include "py/stream.h" +#include "py/frozenmod.h" +#include "lib/mp-readline/readline.h" +#include "../wapy/upython.h" +#include "py/repl.h" + +#include "uzlib/tinf.h" +#include "uzlib/tinflate.c" +#include "uzlib/tinfzlib.c" +#include "uzlib/tinfgzip.c" +#include "uzlib/adler32.c" +#include "uzlib/crc32.c" +#include "crypto-algorithms/sha256.h" +#include "crypto-algorithms/sha256.c" +#include "extmod/virtpin.h" +#include "extmod/machine_mem.h" +#include "extmod/machine_pinbase.h" +#include "extmod/machine_signal.h" +#include "extmod/machine_pulse.h" +#include "extmod/machine_i2c.h" +#include "extmod/machine_spi.h" +#include "py/qstr.h" +#include "extmod/modbluetooth.h" +#include "extmod/moduwebsocket.h" +#include "lib/timeutils/timeutils.h" +#include "extmod/utime_mphal.h" +#include "extmod/misc.h" +#include "lib/utils/interrupt_char.h" +#include "lib/utils/pyexec.h" +#include "ringbuf_b.h" +#include "ringbuf_o.h" +#include "fdfile.h" +#include "mod/ffi/ffi.h" +#include "ffi_common.h" +#include "ffi.h" + + diff --git a/ports/wapy-wasi/file_packager.py b/ports/wapy-wasi/file_packager.py new file mode 120000 index 000000000..ac4074922 --- /dev/null +++ b/ports/wapy-wasi/file_packager.py @@ -0,0 +1 @@ +../wapy-wasm/file_packager.py \ No newline at end of file diff --git a/ports/wapy-wasi/list.c.files b/ports/wapy-wasi/list.c.files new file mode 100644 index 000000000..b124b9715 --- /dev/null +++ b/ports/wapy-wasi/list.c.files @@ -0,0 +1,167 @@ +../../py/qstr.c +../../py/mpprint.c +../../py/vstr.c +../../py/mpstate.c +../../py/malloc.c +../../py/gc.c +../../py/pystack.c +../../py/unicode.c +../../py/mpz.c +../../py/reader.c +../../py/lexer.c +../../py/parse.c +../../py/scope.c +../../py/compile.c +../../py/emitcommon.c +../../py/emitbc.c +../../py/asmbase.c +../../py/asmx64.c +../../py/emitnx64.c +../../py/asmx86.c +../../py/emitnx86.c +../../py/asmthumb.c +../../py/emitnthumb.c +../../py/emitinlinethumb.c +../../py/asmarm.c +../../py/emitnarm.c +../../py/asmxtensa.c +../../py/emitnxtensa.c +../../py/emitinlinextensa.c +../../py/emitnxtensawin.c +../../py/formatfloat.c +../../py/parsenumbase.c +../../py/parsenum.c +../../py/emitglue.c +../../py/persistentcode.c +../../py/../ports/wapy/runtime_no_nlr.c +../../py/runtime_utils.c +../../py/scheduler.c +../../py/nativeglue.c +../../py/pairheap.c +../../py/ringbuf.c +../../py/stackctrl.c +../../py/../ports/wapy/argcheck_no_nlr.c +../../py/warning.c +../../py/profile.c +../../py/map.c +../../py/obj.c +../../py/../ports/wapy/objarray_no_nlr.c +../../py/objattrtuple.c +../../py/objbool.c +../../py/objboundmeth.c +../../py/objcell.c +../../py/objclosure.c +../../py/objcomplex.c +../../py/objdeque.c +../../py/../ports/wapy/objdict_no_nlr.c +../../py/objenumerate.c +../../py/objexcept.c +../../py/objfilter.c +../../py/objfloat.c +../../py/../ports/wapy/objfun_no_nlr.c +../../py/objgenerator.c +../../py/objgetitemiter.c +../../py/objint.c +../../py/objint_longlong.c +../../py/objint_mpz.c +../../py/../ports/wapy/objlist_no_nlr.c +../../py/objmap.c +../../py/objmodule.c +../../py/objobject.c +../../py/objpolyiter.c +../../py/objproperty.c +../../py/objnone.c +../../py/objnamedtuple.c +../../py/objrange.c +../../py/objreversed.c +../../py/objset.c +../../py/objsingleton.c +../../py/objslice.c +../../py/../ports/wapy/objstr_no_nlr.c +../../py/objstrunicode.c +../../py/objstringio.c +../../py/objtuple.c +../../py/objtype.c +../../py/objzip.c +../../py/opmethods.c +../../py/sequence.c +../../py/stream.c +../../py/binary.c +../../py/../ports/wapy/builtinimport_no_nlr.c +../../py/builtinevex.c +../../py/builtinhelp.c +../../py/modarray.c +../../py/../ports/wapy/modbuiltins_no_nlr.c +../../py/modcollections.c +../../py/modgc.c +../../py/modio.c +../../py/modmath.c +../../py/modcmath.c +../../py/modmicropython.c +../../py/modstruct.c +../../py/modsys.c +../../py/moduerrno.c +../../py/modthread.c +../../py/vm.c +../../py/bc.c +../../py/showbc.c +../../py/repl.c +../../py/smallint.c +../../py/frozenmod.c +../../extmod/moduasyncio.c +../../extmod/moductypes.c +../../extmod/modujson.c +../../extmod/modure.c +../../extmod/moduzlib.c +../../extmod/moduheapq.c +../../extmod/modutimeq.c +../../extmod/moduhashlib.c +../../extmod/moducryptolib.c +../../extmod/modubinascii.c +../../extmod/virtpin.c +../../extmod/machine_mem.c +../../extmod/machine_pinbase.c +../../extmod/machine_signal.c +../../extmod/machine_pulse.c +../../extmod/machine_i2c.c +../../extmod/machine_spi.c +../../extmod/modbluetooth.c +../../extmod/modussl_axtls.c +../../extmod/modussl_mbedtls.c +../../extmod/modurandom.c +../../extmod/moduselect.c +../../extmod/moduwebsocket.c +../../extmod/modwebrepl.c +../../extmod/modframebuf.c +../../extmod/vfs.c +../../extmod/vfs_blockdev.c +../../extmod/vfs_reader.c +../../extmod/vfs_posix.c +../../extmod/vfs_posix_file.c +../../extmod/vfs_fat.c +../../extmod/vfs_fat_diskio.c +../../extmod/vfs_fat_file.c +../../extmod/vfs_lfs.c +../../extmod/utime_mphal.c +../../extmod/uos_dupterm.c +../../lib/embed/abort_.c +../../lib/utils/printf.c +build/frozen.c +build/frozen_mpy.c +../wapy/core/vfs.c +../wapy/core/ringbuf_b.c +../wapy/core/ringbuf_o.c +../wapy/core/file.wapy.c +mod/modos.c +mod/modtime.c +mod/moduos_vfs.c +mod/modffi.c +mod/ffi/ffi.c +mod/ffi/types.c +mod/ffi/prep_cif.c +/data/cross/wapy/cmod/wasm/embed/modembed.c +/data/cross/wapy/cmod/wasm/example/example.c +../../lib/utils/stdout_helpers.c +../../lib/utils/pyexec.c +../../lib/utils/interrupt_char.c +../../lib/mp-readline/readline.c diff --git a/ports/wapy-wasi/main.c b/ports/wapy-wasi/main.c new file mode 100644 index 000000000..63bb1974f --- /dev/null +++ b/ports/wapy-wasi/main.c @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include "../wapy/wapy.h" + +#define DBG 0 +#define DLOPEN 0 + +#if !MICROPY_ENABLE_PYSTACK +#error "need MICROPY_ENABLE_PYSTACK (1) +#endif + + +static int g_argc; +static char **g_argv; //[]; + + + +#include "api/wasm_file_api.c" +#include "api/wasm_import_api.c" + +static int KPANIC = 1; + + +extern char* shm_ptr(); + +char ** +copy_argv(int argc, char *argv[]) { + // calculate the contiguous argv buffer size + int length=0; + + size_t ptr_args = argc + 1; + for (int i = 0; i < argc; i++) { + length += (strlen(argv[i]) + 1); + } + char** new_argv = (char**)malloc((ptr_args) * sizeof(char*) + length); + // copy argv into the contiguous buffer + length = 0; + for (int i = 0; i < argc; i++) { + new_argv[i] = &(((char*)new_argv)[(ptr_args * sizeof(char*)) + length]); + strcpy(new_argv[i], argv[i]); + cdbg("52: argv[%d] = %s\n", i, argv[i]); + length += (strlen(argv[i]) + 1); + } + // insert NULL terminating ptr at the end of the ptr array + new_argv[ptr_args-1] = NULL; + return (new_argv); +} + + +#include "../wapy/upython.c" + + +#include "vmsl/vmreg.h" + +#include "vmsl/vmreg.c" + +extern mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +extern mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); +extern mp_obj_t gen_instance_iternext(mp_obj_t self_in); +extern int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); + +//extern mp_obj_t fun_bc_call_pre(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +//extern mp_obj_t fun_bc_call_past(mp_code_state_t *code_state, mp_vm_return_kind_t vm_return_kind, mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); + + +#include + +#if MICROPY_PY_SYS_SETTRACE +#error "248:duplicate symbol: mp_type_code" +/* +wasm-ld: error: duplicate symbol: mp_type_code +>>> defined in libmicropython.a(profile.o) +>>> defined in libmicropython.a(builtinevex.o) +*/ +#include "py/profile.h" +#endif + +//extern +int pyexec_friendly_repl_process_char(int c); +int pyexec_repl_repl_restart(int ret); +int handle_uncaught_exception(void); + +// entry point for implementing core vm parts in python, set via "embed" module +extern void pyv(mp_obj_t value); +extern mp_obj_t pycore(const char *fn); + +// entry point for line tracing +#if 0 //__WASM__ + #pragma message "no embed" + qstr trace_prev_file = 1/*MP_QSTR_*/; + qstr trace_file = 1/*MP_QSTR_*/; + + size_t trace_prev_line; + size_t trace_line; + + int trace_on; +#else +extern qstr trace_prev_file ; +extern size_t trace_prev_line; + +extern qstr trace_file ; +extern size_t trace_line; + +extern int trace_on; +#endif + +#define io_stdin i_main.shm_stdio + +FILE *cc; +FILE *fd_keyvalue; +FILE *fd_syscall; +FILE *fd_logger; + +// to use kb buffer space for scripts +// if (strlen(io_stdin)>=IO_KBD){ io_stdin[IO_KBD] = 0; +#define IO_CODE_DONE { io_stdin[0] = 0; } + +#if MICROPY_PY_THREAD_GIL && MICROPY_PY_THREAD_GIL_VM_DIVISOR +// This needs to be volatile and outside the VM loop so it persists across handling +// of any exceptions. Otherwise it's possible that the VM never gives up the GIL. +volatile int gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; +#endif + + + +// =================== wasi support =================================== +struct timespec t_timespec; +struct timeval t_timeval; +struct timeval wa_tv; +unsigned int wa_ts_nsec; + +void +wa_setenv(const char *key, int value) { + fprintf(fd_keyvalue,"{\"%s\":%d}\n", key, value); +} + +void +wa_syscall(const char *code) { + fprintf(fd_syscall," %s\n", code); +} + +void +__wa_env(const char *key, unsigned int sign, unsigned int bitwidth, void* addr ) { + const char *csign[2] = { "i", "u" }; + fprintf(fd_keyvalue,"{\"%s\": [\"%s%u\", %u]}\n", key, csign[sign], bitwidth, (unsigned int)addr); +} + +#define wa_env(key,x) __wa_env(key, ( x >= 0 && ~x >= 0 ), sizeof(x)*8 , &x ) + +//=========================================================================== + + + + + + +void Py_Init() { + cc = fdopen(3, "r+"); + fd_keyvalue = fdopen(4, "r+"); + fd_syscall = fdopen(5, "r+"); + fd_logger = fdopen(6, "r+"); + +// do not call cpython names directly + wPy_Initialize(); + wPy_NewInterpreter(); + + wa_setenv("shm", (int)shm_ptr()); + +// js<->wa adapters tests +/* + { + unsigned char uc; // beh + wa_env("uc", uc ); // beh + + unsigned int ui; + wa_env("ui", ui ); + + unsigned long long ul; + wa_env("ul", ul ); + } + + { + signed char sc; + wa_env("sc", sc ); + + signed int si; + wa_env("si", si ); + + signed long long sl; + wa_env("sl", sl ); + } +*/ + + wa_env("tsec", wa_tv.tv_sec); + wa_env("tusec", wa_tv.tv_usec ); + wa_env("tnsec", wa_ts_nsec); + + + wa_setenv("PyRun_SimpleString_MAXSIZE", IO_KBD); + wa_setenv("io_port_kbd", (int)&io_stdin[IO_KBD]); + wa_setenv("MP_IO_SIZE", MP_IO_SIZE); + + wa_syscall("window.setTimeout( init_repl_begin , 1000 )"); + + IO_CODE_DONE; + +} + +static bool def_PyRun_SimpleString_is_repl = false ; +static int async_loop = 1; +static int async_state; + + + + +#if MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE +static inline mp_map_elem_t *mp_map_cached_lookup(mp_map_t *map, qstr qst, uint8_t *idx_cache) { + size_t idx = *idx_cache; + mp_obj_t key = MP_OBJ_NEW_QSTR(qst); + mp_map_elem_t *elem = NULL; + if (idx < map->alloc && map->table[idx].key == key) { + elem = &map->table[idx]; + } else { + elem = mp_map_lookup(map, key, MP_MAP_LOOKUP); + if (elem != NULL) { + *idx_cache = (elem - &map->table[0]) & 0xff; + } + } + return elem; +} +#endif + +#include "../wapy/wapy.c" + + + +//*************************************************************************************** + +int io_encode_hex = 1; +//static int loops = 0; + + +#ifdef __ARDUINO__ + #ifndef __CPP__ + #include ARDUINO_HAL + #endif +#endif + + + + + + +int +main(int argc, char *argv[]) { + + if (KPANIC!=0) { + + if (KPANIC>0) { + + g_argc = argc; + g_argv = copy_argv(argc, argv); + + if (argc) + io_encode_hex = !argc; + + KPANIC = 0; + // init + crash_point = &&VM_stackmess; + + cdbg("270: argv[0..%d] env=%s memory=%p\n", argc, getenv("WAPY"), shm_ptr() ); + + if (argc) { + printf("\nnode.js detected, running repl in infinite loop ...\n"); + // do not multiplex output + cc = stdout; + } + + #include "vmsl/vmwarmup.c" + + if (!argc) { + // WASI syscall + return 0; + } + + } else { + printf("\nno guru meditation [%d,%d]", getchar(), fgetc(cc) ); + // FIXME: exitcode + return 1; + } + + } + + + + while (!KPANIC) { + + #include "../wapy/vmsl/vm_loop.c" + +// node.js + if(argc) { + fflush(stdout); + fgets( io_stdin, MP_IO_SHM_SIZE, stdin ); + continue; + } + +// wasi + break; + } + + return 0; +} // main_iteration + + + + +// + + diff --git a/ports/wapy-wasi/mod b/ports/wapy-wasi/mod new file mode 120000 index 000000000..058d8128b --- /dev/null +++ b/ports/wapy-wasi/mod @@ -0,0 +1 @@ +../wapy-wasm/mod \ No newline at end of file diff --git a/ports/wapy-wasi/mpconfigport.h b/ports/wapy-wasi/mpconfigport.h new file mode 100644 index 000000000..88898fe9b --- /dev/null +++ b/ports/wapy-wasi/mpconfigport.h @@ -0,0 +1,491 @@ +#define MODULES_LINKS + +#include "dev_conf.h" + +#if __DEV__ +#define MICROPY_VM_HOOK_BC 1 + +#ifdef STATIC + #undef STATIC +#endif + +#define STATIC // mpconfig.h:1402 => bad pointer cast and bad linking +#endif + + +#define MICROPY_ROM_TEXT_COMPRESSION (0) +#define MICROPY_PY_FSTRING (1) + +#undef NO_NLR +#define NO_NLR (1) +#define WAPY (1) + + +#define False false +#define True true + + +#define mp_hal_ticks_cpu() 0 + +#ifdef MICROPY_OBJ_IMMEDIATE_OBJS +//https://github.com/pfalcon/pycopy/commit/2a362af5bcdeaadd94ddac7f90ea33092a7ac7be +#undef MICROPY_OBJ_IMMEDIATE_OBJS +#define MICROPY_OBJ_IMMEDIATE_OBJS (0) +#endif + +// options to control how Micro Python is built +#define MICROPY_QSTR_BYTES_IN_HASH (1) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_CPYTHON_COMPAT (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +//just in case of long urls and a local cache ? +#define MICROPY_ALLOC_PATH_MAX (1024) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) + +#if 0 + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1) +#else + //#pragma message "build/frozen_mpy.c:7:2: error: 'incompatible MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE'" + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#endif + +#define MICROPY_MODULE_WEAK_LINKS (0) + +#define MICROPY_MODULE_GETATTR (1) +#define MICROPY_MODULE_SPECIAL_METHODS (1) + + +#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool + +#define MICROPY_PY_BUILTINS_FROZENSET (1) + +#define MICROPY_LONGINT_IMPL_MPZ (2) +#if 1 + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#else + #pragma message "build/frozen_mpy.c:12:2: error: 'incompatible MICROPY_LONGINT_IMPL'" + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) +#endif + +#define MPZ_DIG_SIZE (16) + +#define MICROPY_ENABLE_COMPILER (1) +#define MICROPY_ENABLE_EXTERNAL_IMPORT (1) +#define MICROPY_PY_BUILTINS_COMPILE (1) +#define MICROPY_PY_SYS_EXC_INFO (0) // <============================ NON STANDARD PY2 + +#define MICROPY_PERSISTENT_CODE (1) +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_PERSISTENT_CODE_SAVE (1) +//#define MICROPY_EMIT_MACHINE_CODE (1) + + +#define MICROPY_HAS_FILE_READER (1) +#define MICROPY_HELPER_LEXER_UNIX (1) + +#define MICROPY_READER_POSIX (0) +#define MICROPY_VFS_POSIX (0) + +#define HAVE_mp_raw_code_save_file (1) + +#define MICROPY_EMIT_WASM (1) + +//MICROPY_EMIT_INLINE_ASM ((MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA) +//#define MICROPY_EMIT_NATIVE + +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_INLINE_THUMB (0) + + +#if 1 +// 0 0 0 0 ! RuntimeError: memory access out of bounds +// 0 0 0 1 ! silent crash +// 0 0 1 0 = 11.15 Kpy/s + #define MICROPY_STACKLESS (0) + #define MICROPY_STACKLESS_STRICT (0) + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#else +// 1 0 1 1 = 10.6 Kpy/s +// 1 0 1 0 = 10.8 K +// 1 1 1 0 = 10.6 +// 1 1 1 1 = 10.6 + #define MICROPY_STACKLESS (1) + #define MICROPY_STACKLESS_STRICT (1) // <=============== 1! or too much collect + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#endif + +// nlr.h MICROPY_NLR_* must match a supported arch +// define or autodetect will fail to select WASM +#if defined(__WASI__) || defined(__EMSCRIPTEN__) + #define MICROPY_NLR_SETJMP (0) +#else + #define MICROPY_NLR_SETJMP (1) +#endif + +#define MICROPY_DYNAMIC_COMPILER (0) + +#if MICROPY_NLR_SETJMP + // can't have MICROPY_NLR_SETJMP==MICROPY_DYNAMIC_COMPILER==0 + #define MICROPY_NLR_OS_WINDOWS (0) + #define MICROPY_NLR_X86 (0) + #define MICROPY_NLR_X64 (0) +#endif + +// mpconfig +#define MICROPY_PY_GENERATOR_PEND_THROW (1) // def 1 +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) // def 0 + + +// MEMORY +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_GCREGS_SETJMP (1) +#define MICROPY_GC_ALLOC_THRESHOLD (1) + + +//like ESP/UNIX +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) + + +#if MICROPY_PY_LVGL +#define MICROPY_MEM_STATS (0) +#else +#define MICROPY_MEM_STATS (1) +#endif + +#if MICROPY_MEM_STATS +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#endif + + +#define MICROPY_KBD_EXCEPTION (1) + + +//?? +#define MICROPY_USE_INTERNAL_ERRNO (0) + +#define MICROPY_USE_READLINE (0) +#define MICROPY_PY_BUILTINS_INPUT (1) + + +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#define MICROPY_PY_DELATTR_SETATTR (1) +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +#define MICROPY_PY_MATH_FACTORIAL (1) + + +#define MICROPY_COMP_MODULE_CONST (1) + +#if 0 + #define MICROPY_COMP_CONST (0) + #define MICROPY_PY_SYS_SETTRACE (1) +#else + #define MICROPY_COMP_CONST (1) + #define MICROPY_PY_SYS_SETTRACE (0) +#endif +#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1) + +#define MICROPY_ENABLE_SOURCE_LINE (1) + +#define MICROPY_HELPER_REPL (1) + +#define MICROPY_PY___FILE__ (1) + +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) + +#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ASYNC_AWAIT (1) +#define MICROPY_PY_ATTRTUPLE (1) + +#define MICROPY_PY_BTREE (0) + +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_BUILTINS_ENUMERATE (1) +#define MICROPY_PY_BUILTINS_EXECFILE (1) // <============================ NON STANDARD PY2 +#define MICROPY_PY_BUILTINS_FILTER (1) +//#define MICROPY_PY_BUILTINS_FLOAT (1) because MICROPY_FLOAT_IMPL_DOUBLE +#define MICROPY_PY_BUILTINS_HELP_MODULES (0) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_MIN_MAX (1) +#define MICROPY_PY_BUILTINS_NEXT2 (1) // <=============== next2 make next() compat with cpython +#define MICROPY_PY_BUILTINS_PROPERTY (1) +#define MICROPY_PY_BUILTINS_REVERSED (1) +#define MICROPY_PY_BUILTINS_SET (1) +#define MICROPY_PY_BUILTINS_SLICE (1) + +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#if MICROPY_PY_BUILTINS_STR_UNICODE + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (1) +#else + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (0) + #if NO_NLR + //#error "unsupported native storage must be utf8" + #endif +#endif + +#define MICROPY_PY_BUILTINS_STR_CENTER (1) +#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) + +#define MICROPY_PY_COLLECTIONS (1) +#define MICROPY_PY_CMATH (1) + +//? TEST THAT THING ! +#if defined(__EMSCRIPTEN__) + #define MICROPY_PY_FFI (1) +#elif defined(__WASI__) + #define MICROPY_PY_FFI (0) // <== UNSUPPORTED #FIXME: +#else + #define MICROPY_PY_FFI (1) // <======================= NON STANDARD NOT CPY +#endif + +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_PY_GC (1) +#define MICROPY_PY_MATH (1) +#define MICROPY_PY_STRUCT (1) +#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_SYS_MODULES (1) +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_PLATFORM "wasi" + +// IO is cooked and multiplexed via mp_hal_stdout_tx_strn in file.c +// so do not modify those +// https://github.com/python/cpython/blob/v3.7.3/Python/bltinmodule.c#L1849-L1932 + + +#define MICROPY_USE_INTERNAL_PRINTF (0) +#define MICROPY_PY_IO (1) // <=============== only 1 for wasm port +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_IO_RESOURCE_STREAM (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_IO_BYTESIO (1) +#define MICROPY_PY_IO_BUFFEREDWRITER (1) + +#define MICROPY_PY_SYS_STDIO_BUFFER (1) +#define MICROPY_PY_SYS_STDFILES (1) +#define MICROPY_PY_OS_DUPTERM (1) // <==== or js console will get C stdout too ( C-OUT [spam] ) + + +#define MICROPY_PY_THREAD (0) +#define MICROPY_PY_THREAD_GIL (0) +#define MICROPY_PY_TIME (0) + +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UERRNO_ERRORCODE (1) +#define MICROPY_PY_UHEAPQ (1) +#define MICROPY_PY_UHASHLIB (1) + +//F +#define MICROPY_PY_UHASHLIB_SHA1 (0) + +#define MICROPY_PY_UHASHLIB_SHA256 (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_UOS (1) +#define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_URE_SUB (1) + + +//TODO: need #define MICROPY_EVENT_POLL_HOOK + select_select on io demultiplexer. +//F +#define MICROPY_PY_USELECT (0) + + +#define MICROPY_PY_UTIME (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UZLIB (1) + +#define MICROPY_VFS (0) + +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_MPY_CODE_SAVE (1) +#define MICROPY_REPL_EVENT_DRIVEN (1) +#define MICROPY_ENABLE_DOC_STRING (1) + + +//asyncio implemented +#define MICROPY_ENABLE_SCHEDULER (0) +#define MICROPY_SCHEDULER_DEPTH (4) + + +// type definitions for the specific machine + +#define BYTES_PER_WORD (4) + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void*)((mp_uint_t)(p) | 1)) + +// This port is intended to be 32-bit, but unfortunately, int32_t for +// different targets may be defined in different ways - either as int +// or as long. This requires different printf formatting specifiers +// to print such value. So, we avoid int32_t and use int directly. +#define UINT_FMT "%u" +#define INT_FMT "%d" +typedef int mp_int_t; // must be pointer size +typedef unsigned int mp_uint_t; // must be pointer size + +typedef long mp_off_t; + +#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) +//#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn(str, len) + + +extern const struct _mp_obj_module_t mp_module_time; +extern const struct _mp_obj_module_t mp_module_io; +extern const struct _mp_obj_module_t mp_module_os; + +#if MICROPY_PY_UOS_VFS + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos_vfs) }, +#else + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), (mp_obj_t)&mp_module_os }, +#endif + +#if MICROPY_PY_FFI + extern const struct _mp_obj_module_t mp_module_ffi; + #define MICROPY_PY_FFI_DEF { MP_ROM_QSTR(MP_QSTR_ffi), MP_ROM_PTR(&mp_module_ffi) }, +#else + #define MICROPY_PY_FFI_DEF +#endif + +#if MICROPY_PY_JNI + #define MICROPY_PY_JNI_DEF { MP_ROM_QSTR(MP_QSTR_jni), MP_ROM_PTR(&mp_module_jni) }, +#else + #define MICROPY_PY_JNI_DEF +#endif + +#if MICROPY_PY_TERMIOS + #define MICROPY_PY_TERMIOS_DEF { MP_ROM_QSTR(MP_QSTR_termios), MP_ROM_PTR(&mp_module_termios) }, +#else + #define MICROPY_PY_TERMIOS_DEF +#endif + +#if MICROPY_PY_SOCKET + #define MICROPY_PY_SOCKET_DEF { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_socket) }, +#else + #define MICROPY_PY_SOCKET_DEF +#endif + +#if MICROPY_PY_USELECT_POSIX + #define MICROPY_PY_USELECT_DEF { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, +#else + #define MICROPY_PY_USELECT_DEF +#endif + +#define MICROPY_PY_MACHINE_DEF + + +#if MICROPY_PY_LVGL + extern const struct _mp_obj_module_t mp_module_utime; + extern const struct _mp_obj_module_t mp_module_lvgl; + extern const struct _mp_obj_module_t mp_module_lvindev; + extern const struct _mp_obj_module_t mp_module_SDL; + + #include "lib/lv_bindings/lvgl/src/lv_misc/lv_gc.h" + + #define MICROPY_PY_LVGL_DEF \ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvgl), (mp_obj_t)&mp_module_lvgl },\ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvindev), (mp_obj_t)&mp_module_lvindev},\ + { MP_OBJ_NEW_QSTR(MP_QSTR_SDL), (mp_obj_t)&mp_module_SDL }, + + #define MPR_void_mp_lv_user_data void *mp_lv_user_data; + #define MPR_LVGL LV_GC_ROOTS(LV_NO_PREFIX) +#else + #define MICROPY_PY_LVGL_DEF + #define MPR_void_mp_lv_user_data + #define MPR_LVGL +#endif + + +// { MP _ ROM_QSTR(MP_QSTR_umachine), MP _ ROM_PTR(&mp_module_machine) }, + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + MICROPY_PY_FFI_DEF \ + MICROPY_PY_JNI_DEF \ + MICROPY_PY_SOCKET_DEF \ + MICROPY_PY_MACHINE_DEF \ + MICROPY_PY_UOS_DEF \ + MICROPY_PY_USELECT_DEF \ + MICROPY_PY_TERMIOS_DEF \ + MICROPY_PY_LVGL_DEF \ + MODULES_LINKS + + + +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, \ + + + +#define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_binascii), (mp_obj_t)&mp_module_ubinascii }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_collections), (mp_obj_t)&mp_module_collections }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_errno), (mp_obj_t)&mp_module_uerrno }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_hashlib), (mp_obj_t)&mp_module_uhashlib }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_heapq), (mp_obj_t)&mp_module_uheapq }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_json), (mp_obj_t)&mp_module_ujson }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&mp_module_os }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_random), (mp_obj_t)&mp_module_urandom }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_re), (mp_obj_t)&mp_module_ure }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_select), (mp_obj_t)&mp_module_uselect }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_socket), (mp_obj_t)&mp_module_usocket }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_ssl), (mp_obj_t)&mp_module_ussl }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_struct), (mp_obj_t)&mp_module_ustruct }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_zlib), (mp_obj_t)&mp_module_uzlib }, \ + + + +// We need to provide a declaration/definition of alloca() +#include + +#define MICROPY_HW_BOARD_NAME "WaPy" +#define MICROPY_HW_MCU_NAME "wasi" + +#ifdef __linux__ +#define MICROPY_MIN_USE_STDOUT (1) +#endif + +#ifdef __thumb__ +#define MICROPY_MIN_USE_CORTEX_CPU (1) +#define MICROPY_MIN_USE_STM32_MCU (1) +#endif + +#define MP_STATE_PORT MP_STATE_VM + +#define MPR_const_char_readline_hist const char *readline_hist[16]; + +#define MICROPY_PORT_ROOT_POINTERS \ + MPR_LVGL \ + MPR_void_mp_lv_user_data \ + MPR_const_char_readline_hist \ + void *PyOS_InputHook; \ + int coro_call_counter; \ + + +#define FFCONF_H "lib/oofatfs/ffconf.h" + + + + + + + + +// diff --git a/ports/wapy-wasi/mphalport.h b/ports/wapy-wasi/mphalport.h new file mode 120000 index 000000000..f4e81f423 --- /dev/null +++ b/ports/wapy-wasi/mphalport.h @@ -0,0 +1 @@ +../wapy-wasm/mphalport.h \ No newline at end of file diff --git a/ports/wapy-wasi/qstr b/ports/wapy-wasi/qstr new file mode 120000 index 000000000..ddbda9e5d --- /dev/null +++ b/ports/wapy-wasi/qstr @@ -0,0 +1 @@ +../wapy-wasm/qstr \ No newline at end of file diff --git a/ports/wapy-wasi/qstrdefsport.h b/ports/wapy-wasi/qstrdefsport.h new file mode 120000 index 000000000..6cb761a72 --- /dev/null +++ b/ports/wapy-wasi/qstrdefsport.h @@ -0,0 +1 @@ +../wapy/qstrdefsport.h \ No newline at end of file diff --git a/ports/wapy-wasi/vmsl b/ports/wapy-wasi/vmsl new file mode 120000 index 000000000..7b656927b --- /dev/null +++ b/ports/wapy-wasi/vmsl @@ -0,0 +1 @@ +../wapy-wasm/vmsl \ No newline at end of file diff --git a/ports/wapy-wasm/Makefile b/ports/wapy-wasm/Makefile new file mode 100644 index 000000000..ae5ba8024 --- /dev/null +++ b/ports/wapy-wasm/Makefile @@ -0,0 +1,388 @@ +#PORTS=$(EM_CACHE)/asmjs/ports-builds +#FROZEN_MPY_DIR ?= modules +#FROZEN_DIR ?= flash + + +BASENAME=wapy +PROG=$(BASENAME).html +LIBPYTHON = lib$(BASENAME).a +CROSS = 0 +# clang has slightly different options to GCC +#CLANG = 1 + +# compiler settings +ifdef CWARN +else +CFLAGS += $(CWARN) -Wall -Werror +endif + +CFLAGS += -Wpointer-arith -Wuninitialized -ferror-limit=2 + + +include ../../py/mkenv.mk + +#EM_CACHE ?= $(HOME)/.emscripten_cache + +# qstr definitions (must come before including py.mk) +QSTR_DEFS = qstrdefsport.h + +ifdef EMSCRIPTEN + ifdef WASM_FILE_API +# note that WASM_FILE_API will pull filesystem function into the wasm lib + CPPFLAGS += -DWASM_FILE_API=1 + LD_LIB += -s FORCE_FILESYSTEM=1 + COPT += -s FORCE_FILESYSTEM=1 + endif + + CC = emcc + LD = $(CC) + + ifeq ($(DEBUG), 1) + OG = -O0 -g4 + else + #OPTIM=1 + #DLO=1 + CPPFLAGS += -DNDEBUG + OG = -Oz -g0 + endif + + + ifdef LVGL + CPPFLAGS = -DMICROPY_PY_LVGL=1 + JSFLAGS += -s USE_SDL=2 + CFLAGS_USERMOD += -DMICROPY_PY_LVGL=1 -Wno-unused-function -Wno-for-loop-analysis + CPP += -DMICROPY_PY_LVGL=1 + endif + + #Debugging/Optimization + WASM_FLAGS += $(OG) --source-map-base http://localhost:8000/ + LD_SHARED += $(OG) --source-map-base http://localhost:8000/ + + # make clang++ Act like 'emcc' but for preprocessing use + CPP = ${EMSCRIPTEN}/../bin/clang -E -undef -D__CPP__ -D__EMSCRIPTEN__ + CPP += --sysroot $(EMSCRIPTEN)/system + CPP += -include $(BUILD)/clang_predefs.h + CPP += $(addprefix -isystem, $(shell env LC_ALL=C $(CC) $(JSFLAGS) $(CFLAGS_EXTRA) -E -x c++ /dev/null -v 2>&1 |sed -e '/^\#include <...>/,/^End of search/{ //!b };d')) + + CPPFLAGS += -D__WASM__ -DMICROPY_PY_FFI=1 -Wno-unused-variable + CPP += -D__WASM__ -DMICROPY_PY_FFI=1 + CPPFLAGS += $(INC) -Wall -ansi -std=gnu11 + + # NO FFI on upstream for now + +else + CPPFLAGS += $(INC) $(WARN) -DMICROPY_PY_FFI=1 -ansi -std=gnu11 + CPP += -D__CPP__ -D__EMSCRIPTEN__ -DMICROPY_PY_FFI=1 +endif + + +CC += $(OG) +LD += $(OG) + + +# include py core make definitions +include ../../py/py.mk + + +SIZE = echo + +INC += -I. +INC += -I../.. +INC += -I$(BUILD) + + + +#LD_SHARED = -Wl,-map,$@.map -Wl,-dead_strip -Wl,-no_pie +LD_SHARED += -fno-exceptions -fno-rtti + + + + + +ifneq ($(FROZEN_DIR),) +# To use frozen source modules, put your .py files in a subdirectory (eg scripts/) +# and then invoke make with FROZEN_DIR=scripts (be sure to build from scratch). +CPPFLAGS += -DMICROPY_MODULE_FROZEN_STR +endif + +ifneq ($(FROZEN_MPY_DIR),) +# To use frozen bytecode, put your .py files in a subdirectory (eg frozen/) and +# then invoke make with FROZEN_MPY_DIR=frozen (be sure to build from scratch). +# //for qstr.c + +CPPFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool +CPPFLAGS += -DMICROPY_MODULE_FROZEN_MPY +endif + +# //for build/genhdr/qstr.i.last +# QSTR_GEN_EXTRA_CFLAGS=-DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool + +MPY_CROSS_FLAGS += -mcache-lookup-bc + +include ../wapy/wapy.mk + + +SRC_C = \ + ../wapy/core/vfs.c \ + ../wapy/core/ringbuf_b.c \ + ../wapy/core/ringbuf_o.c \ + ../wapy/core/file.wapy.c \ + mod/modos.c \ + mod/modtime.c \ + mod/moduos_vfs.c \ + + +LIB_SRC_C= $(addprefix lib/, \ + utils/stdout_helpers.c \ + utils/pyexec.c \ + utils/interrupt_char.c \ + mp-readline/readline.c \ + ) + +# now using user mod dir, SRC_MOD use USER_C_MODULES + +ifdef LVGL + LIB_SRC_C_EXTRA += $(addprefix lib/,\ + lv_bindings/driver/SDL/SDL_monitor.c \ + lv_bindings/driver/SDL/SDL_mouse.c \ + lv_bindings/driver/SDL/modSDL.c \ + timeutils/timeutils.c \ + ) + CPPFLAGS += -Wno-unused-function -Wno-for-loop-analysis +endif + + +ifdef NDK + SRC_C += ../wapy-wasm/main.c mod/modffi.c +else +# optionnal experimental FFI + SRC_C+= \ + mod/modffi.c \ + mod/ffi/ffi.c \ + mod/ffi/types.c \ + mod/ffi/prep_cif.c +endif + +# List of sources for qstr extraction +SRC_QSTR += $(SRC_C) qstr/objtype.c qstr/modbuiltins.c +SRC_QSTR += $(LIB_SRC_C) + + +# Append any auto-generated sources that are needed by sources listed in +# SRC_QSTR +SRC_QSTR_AUTO_DEPS += SRC_QSTR + +OBJ = $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C_EXTRA:.c=.o)) + + +CPPFLAGS += -DNO_NLR=$(NO_NLR) +CFLAGS += $(CPPFLAGS) $(CFLAGS_EXTRA) + + +all: $(PROG) + +include ../../py/mkrules.mk + + +# one day, maybe go via a *embeddable* static lib first ? +LIBPYTHON = lib$(BASENAME)$(TARGET).a + +#force preprocessor env to be created before qstr extraction +ifdef EMSCRIPTEN +$(BUILD)/clang_predefs.h: + $(Q)mkdir -p $(dir $@) + $(Q)emcc $(CFLAGS) $(JSFLAGS) -E -x c /dev/null -dM > $@ + +# Create `clang_predefs.h` as soon as possible, using a Makefile trick + +Makefile: $(BUILD)/clang_predefs.h +endif + + + +ifdef NDK +COPT += +LD_PROG += +WASM_FLAGS = +endif + +ifndef NDK +COPT += --memory-init-file 0 +COPT += -s NO_EXIT_RUNTIME=1 +COPT += -s ALLOW_MEMORY_GROWTH=0 -s TOTAL_STACK=16777216 +COPT += -s TOTAL_MEMORY=512MB +# COPT+= -s "ENVIRONMENT=web" +# COPT+= -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +# compression and dlopen(wasm) can fail on chrom* ? +# -s LZ4=0 +COPT += $(JSFLAGS) $(WASM_FLAGS) -s ASSERTIONS=2 + +LD_PROG += -s FORCE_FILESYSTEM=1 -s EXPORT_ALL=1 +LD_PROG += -s EXPORTED_FUNCTIONS="['_main', '_shm_ptr', '_shm_get_ptr', '_show_os_loop']" +LD_PROG += -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s LLD_REPORT_UNDEFINED=1 + + +WASM_FLAGS += -s LZ4=1 -s WASM=1 +WASM_FLAGS += -s ASSERTIONS=1 -s DEMANGLE_SUPPORT=1 -s DISABLE_EXCEPTION_CATCHING=1 + +# -s USE_ZLIB=1 -s USE_LIBPNG=1 -s USE_OGG=1 +# COPT += -s USE_BULLET=1 -s USE_FREETYPE=1 +# LD_EXTRA += -lbullet -lfreetype +THR_FLAGS=-s FETCH=1 -s USE_PTHREADS=0 +endif + + +clean: + $(RM) -rf $(BUILD) $(CLEAN_EXTRA) $(LIBPYTHON) + $(shell rm $(BASENAME)/$(BASENAME).* || echo echo test data cleaned up) + + +lib-static: $(OBJ) + $(ECHO) "Linking static $(LIBPYTHON)" + $(Q)$(AR) rcs $(LIBPYTHON) $(OBJ) + + +lib-shared: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).wasm" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o lib$(BASENAME)$(TARGET).wasm $(OBJ) -ldl -lc -s FORCE_FILESYSTEM=1 --use-preload-plugins + +lib-android: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).so" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o lib$(BASENAME)$(TARGET).so $(OBJ) $(LD_EXTRA) + +lib-allshared: $(OBJ) + $(ECHO) "Linking shared lib$(BASENAME)$(TARGET).wasm" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o lib$(BASENAME)$(TARGET).wasm $(OBJ) +# + $(ECHO) "Linking shared libsdl2.wasm" + $(Q)$(LD) $(LD_LIB) $(LD_SHARED) -o libsdl2.wasm -lSDL2 -logg -lfreetype + +libs: lib-static lib-shared + +ifdef HALF +#============ +LD_PROG += $(WASM_FLAGS) -s MAIN_MODULE=1 +LD_LIB += $(WASM_FLAGS) -s SIDE_MODULE=1 +CFLAGS += -fPIC + +# lib-static <= beware to clean the .a could be linked in +$(PROG): lib-allshared + $(ECHO) "Building shared executable $@" + $(shell cp lib$(BASENAME)$(TARGET).wasm lib$(BASENAME)$(TARGET).so) + $(shell cp libsdl2.wasm libsdl2.so) + $(Q)$(CC) $(CFLAGS) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) $(BLOBS)\ + -s BINARYEN_ASYNC_COMPILATION=1 --use-preload-plugins\ + -o $@ main.c -l$(BASENAME)$(TARGET) -lSDL2 -lGL -lEGL -lGLESv2 -ldl -lm + $(shell mv $(BASENAME).* $(BASENAME)/) +#-L. lib$(BASENAME)$(TARGET).a +endif + +ifdef SHARED +#============ +LD_PROG += $(WASM_FLAGS) -s MAIN_MODULE=1 +LD_LIB += $(WASM_FLAGS) -s SIDE_MODULE=1 +CFLAGS += -fPIC + +$(PROG): lib-static lib-allshared + $(ECHO) "Building shared executable $@" + $(shell cp lib$(BASENAME)$(TARGET).wasm lib$(BASENAME)$(TARGET).so) + $(shell cp libsdl2.wasm libsdl2.so) + $(Q)$(CC) $(CFLAGS) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) $(BLOBS)\ + -s BINARYEN_ASYNC_COMPILATION=1 --use-preload-plugins\ + -o $@ main.c -L. -lwapy -lsdl2 -ldl -lm + $(shell mv $(BASENAME).* $(BASENAME)/) + +endif + + +ifdef STATIC_NOPANDA +# ============ static + pic was SERIOUSLY BROKEN until clang 11 ========== +# but this is the good one +CFLAGS += -fPIC +LD_PROG += $(CFLAGS) $(WASM_FLAGS) -s MAIN_MODULE=1 -s LZ4=1 +LD_LIB += $(WASM_FLAGS) -s LINKABLE=1 -s EXPORT_ALL=1 + + +$(PROG): lib-static + $(ECHO) "Building static executable $@" + $(Q)$(CC) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) \ + -o $@ main.c $(OBJ) $(BLOBS) -ldl -lm $(LD_EXTRA) + $(shell mv $(BASENAME).* $(BASENAME)/) +# ============================================================ +endif + + + +ifdef STATIC +# ============ static + pic was SERIOUSLY BROKEN until clang 11 ========== +# but this is the good one +CFLAGS += -fPIC +LD_PROG += $(CFLAGS) $(WASM_FLAGS) -s MAIN_MODULE=1 -s LZ4=1 +LD_LIB += $(WASM_FLAGS) -s LINKABLE=1 -s EXPORT_ALL=1 + +#LD_EXTRA += -lbullet $(LnkP3D) + +$(PROG): lib-static + $(ECHO) "Building static executable $@" + $(Q)$(CC) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) \ + -o $@ main.c lib$(BASENAME)$(TARGET).a $(BLOBS) -s USE_WEBGL2=1 -ldl -lm $(LD_EXTRA) + $(shell mv $(BASENAME).* $(BASENAME)/) +# ============================================================ +endif + + + +ifdef NDK +# ============ static + pic was SERIOUSLY BROKEN until clang 11 ========== +# but this is the good one +LD_PROG += $(CFLAGS) +CFLAGS += -fPIC +LD_LIB = + +$(PROG): lib-android + $(ECHO) "Building static executable $@" + $(CC) $(INC) $(COPT) $(LD_PROG) $(THR_FLAGS) \ + -o $@ main.c -L. -lwapy $(BLOBS) -lEGL -lGLESv2 -ldl -lm $(LD_EXTRA) + $(shell mv $(BASENAME).* $(BASENAME)/) +# ============================================================ +endif + + + +check: + $(ECHO) + $(ECHO) "============================================================" + $(ECHO) "CC=[$(CC)]" + $(ECHO) "CPP=[$(CPP)]" + $(ECHO) "COPT=[$(COPT)]" + $(ECHO) JSFLAGS=$(JSFLAGS) + $(ECHO) CPPFLAGS=$(CPPFLAGS) + $(ECHO) + $(ECHO) CXX=$(CXX) + $(ECHO) AS=$(AS) + $(ECHO) LD=$(LD) + $(ECHO) OBJCOPY=$(OBJCOPY) + $(ECHO) SIZE=$(SIZE) + $(ECHO) STRIP=$(STRIP) + $(ECHO) AR=$(AR) + $(ECHO) +#emscripten specifics + $(ECHO) EMSDK=$(EMSDK) + $(ECHO) UPSTREAM=$(UPSTREAM) + $(ECHO) EMSCRIPTEN=$(EMSCRIPTEN) + $(ECHO) EMSDK_NODE=$(EMSDK_NODE) + $(ECHO) EMSCRIPTEN_TOOLS=$(EMSCRIPTEN_TOOLS) + $(ECHO) EM_CONFIG=$(EM_CONFIG) + $(ECHO) EMMAKEN_COMPILER=$(EMMAKEN_COMPILER) + $(ECHO) EMMAKEN_CFLAGS=$(EMMAKEN_CFLAGS) + $(ECHO) EMCC_FORCE_STDLIBS=$(EMCC_FORCE_STDLIBS) + $(ECHO) EM_CACHE=$(EM_CACHE) + $(ECHO) EM_CONFIG=$(EM_CONFIG) + $(shell env|grep ^EM) + $(ECHO) + $(shell ${EMSCRIPTEN}/../bin/clang -v 2>&1|grep ^clang ) + #terminate on error so python webserver won't start diff --git a/ports/wapy-wasm/README.txt b/ports/wapy-wasm/README.txt new file mode 100644 index 000000000..3e2d3cc3b --- /dev/null +++ b/ports/wapy-wasm/README.txt @@ -0,0 +1,3 @@ +WIP +CPP / C / Assembly skills required +freenode irc , #micropython-fr diff --git a/ports/wapy-wasm/api/wasm_file_api.c b/ports/wapy-wasm/api/wasm_file_api.c new file mode 100644 index 000000000..5812fac4b --- /dev/null +++ b/ports/wapy-wasm/api/wasm_file_api.c @@ -0,0 +1,33 @@ +#include +#include +#include "py/mpconfig.h" + +#ifdef __EMSCRIPTEN__ +#include "emscripten.h" +#endif + +// VERY BAD +int +wasm_file_open(const char *url) { + fprintf(stderr,"10:wasm_file_open[%s]\n", url); + + if (strlen(url)>1 && url[0]==':') { + fprintf(stderr," -> same host[%s]\n", url); + int fidx = EM_ASM_INT({return wasm_file_open(UTF8ToString($0)); }, url ); + + // TODO rebuild local path in ramdisk relative to getcwd() + char fname[MICROPY_ALLOC_PATH_MAX]; + + snprintf(fname, sizeof(fname), "cache_%d", fidx); + return fileno( fopen(fname,"r") ); + } + if ( (strlen(url)>6) && (url[4]==':' || url[5]==':') ) { + fprintf(stderr," -> remote host[%s]\n", url); + int fidx = EM_ASM_INT({return wasm_file_open(UTF8ToString($0)); }, url ); + char fname[20]; + snprintf(fname, sizeof(fname), "cache_%d", fidx); + return fileno( fopen(fname,"r") ); + } + return 0; +} + diff --git a/ports/wapy-wasm/api/wasm_import_api.c b/ports/wapy-wasm/api/wasm_import_api.c new file mode 100644 index 000000000..2e6061177 --- /dev/null +++ b/ports/wapy-wasm/api/wasm_import_api.c @@ -0,0 +1,30 @@ +// TODO: that can't go inside a shared wasm lib + +mp_import_stat_t +wasm_find_module(const char *modname) { + if( access( modname, F_OK ) != -1 ) { + struct stat stats; + stat(modname, &stats); + if (S_ISDIR(stats.st_mode)) + return MP_IMPORT_STAT_DIR; + return MP_IMPORT_STAT_FILE; + } + + int found = EM_ASM_INT({return vm.fsync.exists(UTF8ToString($0), true); }, modname ) ; + + if ( found>0 ) { + int dl = EM_ASM_INT({return vm.fsync.open(UTF8ToString($0),UTF8ToString($0)); }, modname ); + + if (found==1) { + cdbg("19: wasm_find_module: DL FILE %s size=%d ", modname, dl); + return MP_IMPORT_STAT_FILE; + } + if (found==2) { + cdbg("23: wasm_find_module: IS DIR %s size=%d ", modname, dl); + return MP_IMPORT_STAT_DIR; + } + } + cdbg("404:wasm_find_module '%s' (%d)\n", modname, found); + return MP_IMPORT_STAT_NO_EXIST; +} + diff --git a/ports/wapy-wasm/bc_as_integers.h b/ports/wapy-wasm/bc_as_integers.h new file mode 100644 index 000000000..64adfd7f4 --- /dev/null +++ b/ports/wapy-wasm/bc_as_integers.h @@ -0,0 +1,282 @@ + +#undef MP_BC_MASK_FORMAT +#define MP_BC_MASK_FORMAT (240) + +#undef MP_BC_MASK_EXTRA_BYTE +#define MP_BC_MASK_EXTRA_BYTE (158) + +#undef MP_BC_FORMAT_BYTE +#define MP_BC_FORMAT_BYTE (0) + +#undef MP_BC_FORMAT_QSTR +#define MP_BC_FORMAT_QSTR (1) + +#undef MP_BC_FORMAT_VAR_UINT +#define MP_BC_FORMAT_VAR_UINT (2) + +#undef MP_BC_FORMAT_OFFSET +#define MP_BC_FORMAT_OFFSET (3) + +#undef MP_BC_BASE_RESERVED +#define MP_BC_BASE_RESERVED (0) + +#undef MP_BC_BASE_QSTR_O +#define MP_BC_BASE_QSTR_O (16) + +#undef MP_BC_BASE_VINT_E +#define MP_BC_BASE_VINT_E (32) + +#undef MP_BC_BASE_VINT_O +#define MP_BC_BASE_VINT_O (48) + +#undef MP_BC_BASE_JUMP_E +#define MP_BC_BASE_JUMP_E (64) + +#undef MP_BC_BASE_BYTE_O +#define MP_BC_BASE_BYTE_O (80) + +#undef MP_BC_BASE_BYTE_E +#define MP_BC_BASE_BYTE_E (96) + +#undef MP_BC_LOAD_CONST_SMALL_INT_MULTI +#define MP_BC_LOAD_CONST_SMALL_INT_MULTI (112) + +#undef MP_BC_LOAD_FAST_MULTI +#define MP_BC_LOAD_FAST_MULTI (176) + +#undef MP_BC_STORE_FAST_MULTI +#define MP_BC_STORE_FAST_MULTI (192) + +#undef MP_BC_UNARY_OP_MULTI +#define MP_BC_UNARY_OP_MULTI (208) + +#undef MP_BC_BINARY_OP_MULTI +#define MP_BC_BINARY_OP_MULTI (215) + +#undef MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM +#define MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM (64) + +#undef MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS +#define MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS (16) + +#undef MP_BC_LOAD_FAST_MULTI_NUM +#define MP_BC_LOAD_FAST_MULTI_NUM (16) + +#undef MP_BC_STORE_FAST_MULTI_NUM +#define MP_BC_STORE_FAST_MULTI_NUM (16) + +#undef MP_BC_LOAD_CONST_FALSE +#define MP_BC_LOAD_CONST_FALSE (80) + +#undef MP_BC_LOAD_CONST_NONE +#define MP_BC_LOAD_CONST_NONE (81) + +#undef MP_BC_LOAD_CONST_TRUE +#define MP_BC_LOAD_CONST_TRUE (82) + +#undef MP_BC_LOAD_CONST_SMALL_INT +#define MP_BC_LOAD_CONST_SMALL_INT (34) + +#undef MP_BC_LOAD_CONST_STRING +#define MP_BC_LOAD_CONST_STRING (16) + +#undef MP_BC_LOAD_CONST_OBJ +#define MP_BC_LOAD_CONST_OBJ (35) + +#undef MP_BC_LOAD_NULL +#define MP_BC_LOAD_NULL (83) + +#undef MP_BC_LOAD_FAST_N +#define MP_BC_LOAD_FAST_N (36) + +#undef MP_BC_LOAD_DEREF +#define MP_BC_LOAD_DEREF (37) + +#undef MP_BC_LOAD_NAME +#define MP_BC_LOAD_NAME (17) + +#undef MP_BC_LOAD_GLOBAL +#define MP_BC_LOAD_GLOBAL (18) + +#undef MP_BC_LOAD_ATTR +#define MP_BC_LOAD_ATTR (19) + +#undef MP_BC_LOAD_METHOD +#define MP_BC_LOAD_METHOD (20) + +#undef MP_BC_LOAD_SUPER_METHOD +#define MP_BC_LOAD_SUPER_METHOD (21) + +#undef MP_BC_LOAD_BUILD_CLASS +#define MP_BC_LOAD_BUILD_CLASS (84) + +#undef MP_BC_LOAD_SUBSCR +#define MP_BC_LOAD_SUBSCR (85) + +#undef MP_BC_STORE_FAST_N +#define MP_BC_STORE_FAST_N (38) + +#undef MP_BC_STORE_DEREF +#define MP_BC_STORE_DEREF (39) + +#undef MP_BC_STORE_NAME +#define MP_BC_STORE_NAME (22) + +#undef MP_BC_STORE_GLOBAL +#define MP_BC_STORE_GLOBAL (23) + +#undef MP_BC_STORE_ATTR +#define MP_BC_STORE_ATTR (24) + +#undef MP_BC_STORE_SUBSCR +#define MP_BC_STORE_SUBSCR (86) + +#undef MP_BC_DELETE_FAST +#define MP_BC_DELETE_FAST (40) + +#undef MP_BC_DELETE_DEREF +#define MP_BC_DELETE_DEREF (41) + +#undef MP_BC_DELETE_NAME +#define MP_BC_DELETE_NAME (25) + +#undef MP_BC_DELETE_GLOBAL +#define MP_BC_DELETE_GLOBAL (26) + +#undef MP_BC_DUP_TOP +#define MP_BC_DUP_TOP (87) + +#undef MP_BC_DUP_TOP_TWO +#define MP_BC_DUP_TOP_TWO (88) + +#undef MP_BC_POP_TOP +#define MP_BC_POP_TOP (89) + +#undef MP_BC_ROT_TWO +#define MP_BC_ROT_TWO (90) + +#undef MP_BC_ROT_THREE +#define MP_BC_ROT_THREE (91) + +#undef MP_BC_JUMP +#define MP_BC_JUMP (66) + +#undef MP_BC_POP_JUMP_IF_TRUE +#define MP_BC_POP_JUMP_IF_TRUE (67) + +#undef MP_BC_POP_JUMP_IF_FALSE +#define MP_BC_POP_JUMP_IF_FALSE (68) + +#undef MP_BC_JUMP_IF_TRUE_OR_POP +#define MP_BC_JUMP_IF_TRUE_OR_POP (69) + +#undef MP_BC_JUMP_IF_FALSE_OR_POP +#define MP_BC_JUMP_IF_FALSE_OR_POP (70) + +#undef MP_BC_UNWIND_JUMP +#define MP_BC_UNWIND_JUMP (64) + +#undef MP_BC_SETUP_WITH +#define MP_BC_SETUP_WITH (71) + +#undef MP_BC_SETUP_EXCEPT +#define MP_BC_SETUP_EXCEPT (72) + +#undef MP_BC_SETUP_FINALLY +#define MP_BC_SETUP_FINALLY (73) + +#undef MP_BC_POP_EXCEPT_JUMP +#define MP_BC_POP_EXCEPT_JUMP (74) + +#undef MP_BC_FOR_ITER +#define MP_BC_FOR_ITER (75) + +#undef MP_BC_WITH_CLEANUP +#define MP_BC_WITH_CLEANUP (92) + +#undef MP_BC_END_FINALLY +#define MP_BC_END_FINALLY (93) + +#undef MP_BC_GET_ITER +#define MP_BC_GET_ITER (94) + +#undef MP_BC_GET_ITER_STACK +#define MP_BC_GET_ITER_STACK (95) + +#undef MP_BC_BUILD_TUPLE +#define MP_BC_BUILD_TUPLE (42) + +#undef MP_BC_BUILD_LIST +#define MP_BC_BUILD_LIST (43) + +#undef MP_BC_BUILD_MAP +#define MP_BC_BUILD_MAP (44) + +#undef MP_BC_STORE_MAP +#define MP_BC_STORE_MAP (98) + +#undef MP_BC_BUILD_SET +#define MP_BC_BUILD_SET (45) + +#undef MP_BC_BUILD_SLICE +#define MP_BC_BUILD_SLICE (46) + +#undef MP_BC_STORE_COMP +#define MP_BC_STORE_COMP (47) + +#undef MP_BC_UNPACK_SEQUENCE +#define MP_BC_UNPACK_SEQUENCE (48) + +#undef MP_BC_UNPACK_EX +#define MP_BC_UNPACK_EX (49) + +#undef MP_BC_RETURN_VALUE +#define MP_BC_RETURN_VALUE (99) + +#undef MP_BC_RAISE_LAST +#define MP_BC_RAISE_LAST (100) + +#undef MP_BC_RAISE_OBJ +#define MP_BC_RAISE_OBJ (101) + +#undef MP_BC_RAISE_FROM +#define MP_BC_RAISE_FROM (102) + +#undef MP_BC_YIELD_VALUE +#define MP_BC_YIELD_VALUE (103) + +#undef MP_BC_YIELD_FROM +#define MP_BC_YIELD_FROM (104) + +#undef MP_BC_MAKE_FUNCTION +#define MP_BC_MAKE_FUNCTION (50) + +#undef MP_BC_MAKE_FUNCTION_DEFARGS +#define MP_BC_MAKE_FUNCTION_DEFARGS (51) + +#undef MP_BC_MAKE_CLOSURE +#define MP_BC_MAKE_CLOSURE (32) + +#undef MP_BC_MAKE_CLOSURE_DEFARGS +#define MP_BC_MAKE_CLOSURE_DEFARGS (33) + +#undef MP_BC_CALL_FUNCTION +#define MP_BC_CALL_FUNCTION (52) + +#undef MP_BC_CALL_FUNCTION_VAR_KW +#define MP_BC_CALL_FUNCTION_VAR_KW (53) + +#undef MP_BC_CALL_METHOD +#define MP_BC_CALL_METHOD (54) + +#undef MP_BC_CALL_METHOD_VAR_KW +#define MP_BC_CALL_METHOD_VAR_KW (55) + +#undef MP_BC_IMPORT_NAME +#define MP_BC_IMPORT_NAME (27) + +#undef MP_BC_IMPORT_FROM +#define MP_BC_IMPORT_FROM (28) + +#undef MP_BC_IMPORT_STAR +#define MP_BC_IMPORT_STAR (105) diff --git a/ports/wapy-wasm/dev_conf.h b/ports/wapy-wasm/dev_conf.h new file mode 100644 index 000000000..505ca3a07 --- /dev/null +++ b/ports/wapy-wasm/dev_conf.h @@ -0,0 +1,3 @@ +#ifndef __DEV__ +#define __DEV__ 1 +#endif diff --git a/ports/wapy-wasm/file_packager.py b/ports/wapy-wasm/file_packager.py new file mode 100755 index 000000000..8fa86cc43 --- /dev/null +++ b/ports/wapy-wasm/file_packager.py @@ -0,0 +1,1005 @@ +#!/usr/bin/env python3.8 +# Copyright 2012 The Emscripten Authors. All rights reserved. +# Emscripten is available under two separate licenses, the MIT license and the +# University of Illinois/NCSA Open Source License. Both these licenses can be +# found in the LICENSE file. + +""" +A tool that generates FS API calls to generate a filesystem, and packages the files +to work with that. + +This is called by emcc. You can also call it yourself. + +You can split your files into "asset bundles", and create each bundle separately +with this tool. Then just include the generated js for each and they will load +the data and prepare it accordingly. This allows you to share assets and reduce +data downloads. + + * If you run this yourself, separately/standalone from emcc, then the main program + compiled by emcc must be built with filesystem support. You can do that with + -s FORCE_FILESYSTEM=1 (if you forget that, an unoptimized build or one with + ASSERTIONS enabled will show an error suggesting you use that flag). + +Usage: + + file_packager.py TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--no-heap-copy] [--separate-metadata] [--lz4] [--use-preload-plugins] + + --preload , + --embed See emcc --help for more details on those options. + + --exclude E [F..] Specifies filename pattern matches to use for excluding given files from being added to the package. + See https://docs.python.org/2/library/fnmatch.html for syntax. + + --from-emcc Indicate that `file_packager.py` was called from `emcc.py` and will be further processed by it, so some code generation can be skipped here + + --js-output=FILE Writes output in FILE, if not specified, standard output is used. + + --export-name=EXPORT_NAME Use custom export name (default is `Module`) + + --no-force Don't create output if no valid input file is specified. + + --use-preload-cache Stores package in IndexedDB so that subsequent loads don't need to do XHR. Checks package version. + + --indexedDB-name Use specified IndexedDB database name (Default: 'EM_PRELOAD_CACHE') + + --no-heap-copy If specified, the preloaded filesystem is not copied inside the Emscripten HEAP, but kept in a separate typed array outside it. + The default, if this is not specified, is to embed the VFS inside the HEAP, so that mmap()ing files in it is a no-op. + Passing this flag optimizes for fread() usage, omitting it optimizes for mmap() usage. + + --separate-metadata Stores package metadata separately. Only applicable when preloading and js-output file is specified. + + --lz4 Uses LZ4. This compresses the data using LZ4 when this utility is run, then the client decompresses chunks on the fly, avoiding storing + the entire decompressed data in memory at once. See LZ4 in src/settings.js, you must build the main program with that flag. + + --use-preload-plugins Tells the file packager to run preload plugins on the files as they are loaded. This performs tasks like decoding images + and audio using the browser's codecs. + +Notes: + + * The file packager generates unix-style file paths. So if you are on windows and a file is accessed at + subdir\file, in JS it will be subdir/file. For simplicity we treat the web platform as a *NIX. +""" + +import os +import sys +import shutil +import random +import uuid +import ctypes + +sys.path.insert(1, f"{os.environ['EMSDK']}/upstream/emscripten") + +def make_hash(data): + return __import__('hashlib').md5(data.encode('utf-8')).hexdigest() + +PYDIR='/tmp/py' +try: + if not os.path.isdir(PYDIR): + os.mkdir(PYDIR) +except: + raise SystemExit(f"{PYDIR} is not writeable") + + +try: + #new and shiny + from future_fstrings import fstring_decode +except: + #old and buggy + try: + from fstrings_helper import decode as fstring_decode + except: + raise SystemExit(f"future_fstrings[rewrite] or fstrings_helper not installed") + +import posixpath +from tools import shared +from tools.jsrun import run_js +from subprocess import PIPE +import fnmatch +import json + +if len(sys.argv) == 1: + print( + """Usage: file_packager.py TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--no-heap-copy] [--separate-metadata] [--lz4] [--use-preload-plugins] +See the source for more details.""" + ) + sys.exit(0) + +DEBUG = os.environ.get("EMCC_DEBUG") + +data_target = sys.argv[1] + +IMAGE_SUFFIXES = (".jpg", ".png", ".bmp") +AUDIO_SUFFIXES = (".ogg", ".wav", ".mp3") +AUDIO_MIMETYPES = {"ogg": "audio/ogg", "wav": "audio/wav", "mp3": "audio/mpeg"} + +DDS_HEADER_SIZE = 128 + +# Set to 1 to randomize file order and add some padding, +# to work around silly av false positives +AV_WORKAROUND = 0 + +excluded_patterns = [] +new_data_files = [] + + +def has_hidden_attribute(filepath): + """Win32 code to test whether the given file has the hidden property set.""" + + if sys.platform != "win32": + return False + + try: + attrs = ctypes.windll.kernel32.GetFileAttributesW("%s" % filepath) + assert attrs != -1 + result = bool(attrs & 2) + except Exception: + result = False + return result + + +def should_ignore(fullname, is_file=0): + """The packager should never preload/embed files if the file + is hidden (Win32) or it matches any pattern specified in --exclude""" + + global process_filter + process_filter = None + + if has_hidden_attribute(fullname): + return True + + for p in excluded_patterns: + if fnmatch.fnmatch(fullname, p): + return True + + if is_file and fullname.endswith('.py'): + process_filter = f"{PYDIR}/{make_hash(fullname)}" + with open(fullname,'rb') as src, open(process_filter,'wb') as dst: + content, _ = fstring_decode(src.read()) + dst.write( content.encode('UTF-8') ) + print(fullname, process_filter, file=sys.stderr) + + return False + + +def add(mode, rootpathsrc, rootpathdst): + """Expand directories into individual files + + rootpathsrc: The path name of the root directory on the local FS we are + adding to emscripten virtual FS. + rootpathdst: The name we want to make the source path available on the + emscripten virtual FS. + """ + for dirpath, dirnames, filenames in os.walk(rootpathsrc): + new_dirnames = [] + for name in dirnames: + fullname = os.path.join(dirpath, name) + if not should_ignore(fullname): + new_dirnames.append(name) + elif DEBUG: + print( + 'Skipping directory "%s" from inclusion in the emscripten ' "virtual file system." % fullname, file=sys.stderr + ) + for name in filenames: + fullname = os.path.join(dirpath, name) + if not should_ignore(fullname, is_file=1): + # Convert source filename relative to root directory of target FS. + dstpath = os.path.join(rootpathdst, os.path.relpath(fullname, rootpathsrc)) + if process_filter is None: + new_data_files.append({"srcpath": fullname, "dstpath": dstpath, "mode": mode, "explicit_dst_path": True}) + else: + new_data_files.append({"srcpath": process_filter, "dstpath": dstpath, "mode": mode, "explicit_dst_path": True}) + elif DEBUG: + print('Skipping file "%s" from inclusion in the emscripten ' "virtual file system." % fullname, file=sys.stderr) + del dirnames[:] + dirnames.extend(new_dirnames) + + +def main(): + data_files = [] + export_name = "Module" + leading = "" + has_preloaded = False + plugins = [] + jsoutput = None + from_emcc = False + force = True + # If set to True, IndexedDB (IDBFS in library_idbfs.js) is used to locally + # cache VFS XHR so that subsequent page loads can read the data from the + # offline cache instead. + use_preload_cache = False + indexeddb_name = "EM_PRELOAD_CACHE" + # If set to True, the blob received from XHR is moved to the Emscripten HEAP, + # optimizing for mmap() performance (if ALLOW_MEMORY_GROWTH=0). + # If set to False, the XHR blob is kept intact, and fread()s etc. are performed + # directly to that data. This optimizes for minimal memory usage and fread() + # performance. + heap_copy = True + # If set to True, the package metadata is stored separately from js-output + # file which makes js-output file immutable to the package content changes. + # If set to False, the package metadata is stored inside the js-output file + # which makes js-output file to mutate on each invocation of this packager tool. + separate_metadata = False + lz4 = False + use_preload_plugins = False + + for arg in sys.argv[2:]: + if arg == "--preload": + has_preloaded = True + leading = "preload" + elif arg == "--embed": + leading = "embed" + elif arg == "--exclude": + leading = "exclude" + elif arg == "--no-force": + force = False + leading = "" + elif arg == "--use-preload-cache": + use_preload_cache = True + leading = "" + elif arg.startswith("--indexedDB-name"): + indexeddb_name = arg.split("=", 1)[1] if "=" in arg else None + leading = "" + elif arg == "--no-heap-copy": + heap_copy = False + leading = "" + elif arg == "--separate-metadata": + separate_metadata = True + leading = "" + elif arg == "--lz4": + lz4 = True + leading = "" + elif arg == "--use-preload-plugins": + use_preload_plugins = True + leading = "" + elif arg.startswith("--js-output"): + jsoutput = arg.split("=", 1)[1] if "=" in arg else None + leading = "" + elif arg.startswith("--export-name"): + if "=" in arg: + export_name = arg.split("=", 1)[1] + leading = "" + elif arg.startswith("--from-emcc"): + from_emcc = True + leading = "" + elif arg.startswith("--plugin"): + with open(arg.split("=", 1)[1]) as f: + plugin = f.read() + eval(plugin) # should append itself to plugins + leading = "" + elif leading == "preload" or leading == "embed": + mode = leading + # position of @ if we're doing 'src@dst'. '__' is used to keep the index + # same with the original if they escaped with '@@'. + at_position = arg.replace("@@", "__").find("@") + # '@@' in input string means there is an actual @ character, a single '@' + # means the 'src@dst' notation. + uses_at_notation = at_position != -1 + + if uses_at_notation: + srcpath = arg[0:at_position].replace("@@", "@") # split around the @ + dstpath = arg[at_position + 1 :].replace("@@", "@") + else: + # Use source path as destination path. + srcpath = dstpath = arg.replace("@@", "@") + if os.path.isfile(srcpath) or os.path.isdir(srcpath): + data_files.append({"srcpath": srcpath, "dstpath": dstpath, "mode": mode, "explicit_dst_path": uses_at_notation}) + else: + print("Warning: " + arg + " does not exist, ignoring.", file=sys.stderr) + elif leading == "exclude": + excluded_patterns.append(arg) + else: + print("Unknown parameter:", arg, file=sys.stderr) + sys.exit(1) + + if (not force) and not data_files: + has_preloaded = False + if not has_preloaded or jsoutput is None: + assert not separate_metadata, "cannot separate-metadata without both --preloaded files " "and a specified --js-output" + + if not from_emcc: + print( + "Remember to build the main file with -s FORCE_FILESYSTEM=1 " + "so that it includes support for loading this file package", + file=sys.stderr, + ) + + ret = "" + # emcc.py will add this to the output itself, so it is only needed for + # standalone calls + if not from_emcc: + ret = """ + var Module = typeof %(EXPORT_NAME)s !== 'undefined' ? %(EXPORT_NAME)s : {}; + """ % { + "EXPORT_NAME": export_name + } + + ret += """ + if (!Module.expectedDataFileDownloads) { + Module.expectedDataFileDownloads = 0; + Module.finishedDataFileDownloads = 0; + } + Module.expectedDataFileDownloads++; + (function() { + var loadPackage = function(metadata) { + """ + + code = """ + function assert(check, msg) { + if (!check) throw msg + new Error().stack; + } + """ + + for file_ in data_files: + if not should_ignore(file_["srcpath"]): + if os.path.isdir(file_["srcpath"]): + add(file_["mode"], file_["srcpath"], file_["dstpath"]) + else: + should_ignore(file_["srcpath"], is_file=1) + if process_filter: + newfile = {**file_} + newfile["srcpath"] = process_filter + new_data_files.append(newfile) + print(newfile, file=sys.stderr) + else: + new_data_files.append(file_) + + data_files = [file_ for file_ in new_data_files if not os.path.isdir(file_["srcpath"])] + if len(data_files) == 0: + print("Nothing to do!", file=sys.stderr) + sys.exit(1) + + # Absolutize paths, and check that they make sense + # os.getcwd() always returns the hard path with any symbolic links resolved, + # even if we cd'd into a symbolic link. + curr_abspath = os.path.abspath(os.getcwd()) + + for file_ in data_files: + if not file_["explicit_dst_path"]: + # This file was not defined with src@dst, so we inferred the destination + # from the source. In that case, we require that the destination not be + # under the current location + path = file_["dstpath"] + # Use os.path.realpath to resolve any symbolic links to hard paths, + # to match the structure in curr_abspath. + abspath = os.path.realpath(os.path.abspath(path)) + if DEBUG: + print(path, abspath, curr_abspath, file=sys.stderr) + if not abspath.startswith(curr_abspath): + print( + 'Error: Embedding "%s" which is below the current directory ' + '"%s". This is invalid since the current directory becomes the ' + "root that the generated code will see" % (path, curr_abspath), + file=sys.stderr, + ) + sys.exit(1) + file_["dstpath"] = abspath[len(curr_abspath) + 1 :] + if os.path.isabs(path): + print( + 'Warning: Embedding an absolute file/directory name "%s" to the ' + "virtual filesystem. The file will be made available in the " + 'relative path "%s". You can use the explicit syntax ' + "--preload-file srcpath@dstpath to explicitly specify the target " + "location the absolute source path should be directed to." % (path, file_["dstpath"]), + file=sys.stderr, + ) + + for file_ in data_files: + # name in the filesystem, native and emulated + file_["dstpath"] = file_["dstpath"].replace(os.path.sep, "/") + # If user has submitted a directory name as the destination but omitted + # the destination filename, use the filename from source file + if file_["dstpath"].endswith("/"): + file_["dstpath"] = file_["dstpath"] + os.path.basename(file_["srcpath"]) + # make destination path always relative to the root + file_["dstpath"] = posixpath.normpath(os.path.join("/", file_["dstpath"])) + if DEBUG: + print('Packaging file "%s" to VFS in path "%s".' % (file_["srcpath"], file_["dstpath"]), file=sys.stderr) + + # Remove duplicates (can occur naively, for example preload dir/, preload dir/subdir/) + seen = {} + + def was_seen(name): + if seen.get(name): + return True + seen[name] = 1 + return False + + data_files = [file_ for file_ in data_files if not was_seen(file_["dstpath"])] + + if AV_WORKAROUND: + random.shuffle(data_files) + + # Apply plugins + for file_ in data_files: + for plugin in plugins: + plugin(file_) + + metadata = {"files": []} + + # Set up folders + partial_dirs = [] + for file_ in data_files: + dirname = os.path.dirname(file_["dstpath"]) + dirname = dirname.lstrip("/") # absolute paths start with '/', remove that + if dirname != "": + parts = dirname.split("/") + for i in range(len(parts)): + partial = "/".join(parts[: i + 1]) + if partial not in partial_dirs: + code += """Module['FS_createPath']('/%s', '%s', true, true);\n""" % ("/".join(parts[:i]), parts[i]) + partial_dirs.append(partial) + + if has_preloaded: + # Bundle all datafiles into one archive. Avoids doing lots of simultaneous + # XHRs which has overhead. + start = 0 + with open(data_target, "wb") as data: + for file_ in data_files: + file_["data_start"] = start + with open(file_["srcpath"], "rb") as f: + curr = f.read() + file_["data_end"] = start + len(curr) + if AV_WORKAROUND: + curr += "\x00" + start += len(curr) + data.write(curr) + + # TODO: sha256sum on data_target + if start > 256 * 1024 * 1024: + print( + "warning: file packager is creating an asset bundle of %d MB. " + "this is very large, and browsers might have trouble loading it. " + "see https://hacks.mozilla.org/2015/02/synchronous-execution-and-filesystem-access-in-emscripten/" + % (start / (1024 * 1024)), + file=sys.stderr, + ) + + create_preloaded = """ + Module['FS_createPreloadedFile'](this.name, null, byteArray, true, true, function() { + Module['removeRunDependency']('fp ' + that.name); + }, function() { + if (that.audio) { + Module['removeRunDependency']('fp ' + that.name); // workaround for chromium bug 124926 (still no audio with this, but at least we don't hang) + } else { + err('Preloading file ' + that.name + ' failed'); + } + }, false, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change + """ + create_data = """ + Module['FS_createDataFile'](this.name, null, byteArray, true, true, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change + Module['removeRunDependency']('fp ' + that.name); + """ + + # Data requests - for getting a block of data out of the big archive - have + # a similar API to XHRs + code += """ + /** @constructor */ + function DataRequest(start, end, audio) { + this.start = start; + this.end = end; + this.audio = audio; + } + DataRequest.prototype = { + requests: {}, + open: function(mode, name) { + this.name = name; + this.requests[name] = this; + Module['addRunDependency']('fp ' + this.name); + }, + send: function() {}, + onload: function() { + var byteArray = this.byteArray.subarray(this.start, this.end); + this.finish(byteArray); + }, + finish: function(byteArray) { + var that = this; + %s + this.requests[this.name] = null; + } + }; + %s + """ % ( + create_preloaded if use_preload_plugins else create_data, + """ + var files = metadata['files']; + for (var i = 0; i < files.length; ++i) { + new DataRequest(files[i]['start'], files[i]['end'], files[i]['audio']).open('GET', files[i]['filename']); + } + """ + if not lz4 + else "", + ) + + counter = 0 + for file_ in data_files: + filename = file_["dstpath"] + dirname = os.path.dirname(filename) + basename = os.path.basename(filename) + if file_["mode"] == "embed": + # Embed + data = list(bytearray(open(file_["srcpath"], "rb").read())) + code += """var fileData%d = [];\n""" % counter + if data: + parts = [] + chunk_size = 10240 + start = 0 + while start < len(data): + parts.append( + """fileData%d.push.apply(fileData%d, %s);\n""" % (counter, counter, str(data[start : start + chunk_size])) + ) + start += chunk_size + code += "".join(parts) + code += """Module['FS_createDataFile']('%s', '%s', fileData%d, true, true, false);\n""" % (dirname, basename, counter) + counter += 1 + elif file_["mode"] == "preload": + # Preload + counter += 1 + metadata["files"].append( + { + "filename": file_["dstpath"], + "start": file_["data_start"], + "end": file_["data_end"], + "audio": 1 if filename[-4:] in AUDIO_SUFFIXES else 0, + } + ) + else: + assert 0 + + if has_preloaded: + if not lz4: + # Get the big archive and split it up + if heap_copy: + use_data = """ + // copy the entire loaded file into a spot in the heap. Files will refer to slices in that. They cannot be freed though + // (we may be allocating before malloc is ready, during startup). + var ptr = Module['getMemory'](byteArray.length); + Module['HEAPU8'].set(byteArray, ptr); + DataRequest.prototype.byteArray = Module['HEAPU8'].subarray(ptr, ptr+byteArray.length); + """ + else: + use_data = """ + // Reuse the bytearray from the XHR as the source for file reads. + DataRequest.prototype.byteArray = byteArray; + """ + use_data += """ + var files = metadata['files']; + for (var i = 0; i < files.length; ++i) { + DataRequest.prototype.requests[files[i].filename].onload(); + } + """ + use_data += " Module['removeRunDependency']('datafile_%s');\n" % shared.JS.escape_for_js_string(data_target) + + else: + # LZ4FS usage + temp = data_target + ".orig" + shutil.move(data_target, temp) + meta = run_js( + shared.path_from_root("tools", "lz4-compress.js"), + shared.NODE_JS, + [shared.path_from_root("src", "mini-lz4.js"), temp, data_target], + stdout=PIPE, + ) + os.unlink(temp) + use_data = """ + var compressedData = %s; + compressedData['data'] = byteArray; + assert(typeof LZ4 === 'object', 'LZ4 not present - was your app build with -s LZ4=1 ?'); + LZ4.loadPackage({ 'metadata': metadata, 'compressedData': compressedData }); + Module['removeRunDependency']('datafile_%s'); + """ % ( + meta, + shared.JS.escape_for_js_string(data_target), + ) + + package_uuid = uuid.uuid4() + package_name = data_target + remote_package_size = os.path.getsize(package_name) + remote_package_name = os.path.basename(package_name) + ret += r""" + var PACKAGE_PATH; + if (typeof window === 'object') { + PACKAGE_PATH = window['encodeURIComponent'](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf('/')) + '/'); + } else if (typeof location !== 'undefined') { + // worker + PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf('/')) + '/'); + } else { + throw 'using preloaded data can only be done on a web page or in a web worker'; + } + var PACKAGE_NAME = '%s'; + var REMOTE_PACKAGE_BASE = '%s'; + if (typeof Module['locateFilePackage'] === 'function' && !Module['locateFile']) { + Module['locateFile'] = Module['locateFilePackage']; + err('warning: you defined Module.locateFilePackage, that has been renamed to Module.locateFile (using your locateFilePackage for now)'); + } + var REMOTE_PACKAGE_NAME = Module['locateFile'] ? Module['locateFile'](REMOTE_PACKAGE_BASE, '') : REMOTE_PACKAGE_BASE; + """ % ( + shared.JS.escape_for_js_string(data_target), + shared.JS.escape_for_js_string(remote_package_name), + ) + metadata["remote_package_size"] = remote_package_size + metadata["package_uuid"] = str(package_uuid) + ret += """ + var REMOTE_PACKAGE_SIZE = metadata['remote_package_size']; + var PACKAGE_UUID = metadata['package_uuid']; + """ + + if use_preload_cache: + code += ( + r''' + var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + var IDB_RO = "readonly"; + var IDB_RW = "readwrite"; + var DB_NAME = "''' + + indexeddb_name + + """"; + var DB_VERSION = 1; + var METADATA_STORE_NAME = 'METADATA'; + var PACKAGE_STORE_NAME = 'PACKAGES'; + function openDatabase(callback, errback) { + try { + var openRequest = indexedDB.open(DB_NAME, DB_VERSION); + } catch (e) { + return errback(e); + } + openRequest.onupgradeneeded = function(event) { + var db = event.target.result; + + if(db.objectStoreNames.contains(PACKAGE_STORE_NAME)) { + db.deleteObjectStore(PACKAGE_STORE_NAME); + } + var packages = db.createObjectStore(PACKAGE_STORE_NAME); + + if(db.objectStoreNames.contains(METADATA_STORE_NAME)) { + db.deleteObjectStore(METADATA_STORE_NAME); + } + var metadata = db.createObjectStore(METADATA_STORE_NAME); + }; + openRequest.onsuccess = function(event) { + var db = event.target.result; + callback(db); + }; + openRequest.onerror = function(error) { + errback(error); + }; + }; + + // This is needed as chromium has a limit on per-entry files in IndexedDB + // https://cs.chromium.org/chromium/src/content/renderer/indexed_db/webidbdatabase_impl.cc?type=cs&sq=package:chromium&g=0&l=177 + // https://cs.chromium.org/chromium/src/out/Debug/gen/third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h?type=cs&sq=package:chromium&g=0&l=60 + // We set the chunk size to 64MB to stay well-below the limit + var CHUNK_SIZE = 64 * 1024 * 1024; + + function cacheRemotePackage( + db, + packageName, + packageData, + packageMeta, + callback, + errback + ) { + var transactionPackages = db.transaction([PACKAGE_STORE_NAME], IDB_RW); + var packages = transactionPackages.objectStore(PACKAGE_STORE_NAME); + var chunkSliceStart = 0; + var nextChunkSliceStart = 0; + var chunkCount = Math.ceil(packageData.byteLength / CHUNK_SIZE); + var finishedChunks = 0; + for (var chunkId = 0; chunkId < chunkCount; chunkId++) { + nextChunkSliceStart += CHUNK_SIZE; + var putPackageRequest = packages.put( + packageData.slice(chunkSliceStart, nextChunkSliceStart), + 'package/' + packageName + '/' + chunkId + ); + chunkSliceStart = nextChunkSliceStart; + putPackageRequest.onsuccess = function(event) { + finishedChunks++; + if (finishedChunks == chunkCount) { + var transaction_metadata = db.transaction( + [METADATA_STORE_NAME], + IDB_RW + ); + var metadata = transaction_metadata.objectStore(METADATA_STORE_NAME); + var putMetadataRequest = metadata.put( + { + 'uuid': packageMeta.uuid, + 'chunkCount': chunkCount + }, + 'metadata/' + packageName + ); + putMetadataRequest.onsuccess = function(event) { + callback(packageData); + }; + putMetadataRequest.onerror = function(error) { + errback(error); + }; + } + }; + putPackageRequest.onerror = function(error) { + errback(error); + }; + } + } + + /* Check if there's a cached package, and if so whether it's the latest available */ + function checkCachedPackage(db, packageName, callback, errback) { + var transaction = db.transaction([METADATA_STORE_NAME], IDB_RO); + var metadata = transaction.objectStore(METADATA_STORE_NAME); + var getRequest = metadata.get('metadata/' + packageName); + getRequest.onsuccess = function(event) { + var result = event.target.result; + if (!result) { + return callback(false, null); + } else { + return callback(PACKAGE_UUID === result['uuid'], result); + } + }; + getRequest.onerror = function(error) { + errback(error); + }; + } + + function fetchCachedPackage(db, packageName, metadata, callback, errback) { + var transaction = db.transaction([PACKAGE_STORE_NAME], IDB_RO); + var packages = transaction.objectStore(PACKAGE_STORE_NAME); + + var chunksDone = 0; + var totalSize = 0; + var chunkCount = metadata['chunkCount']; + var chunks = new Array(chunkCount); + + for (var chunkId = 0; chunkId < chunkCount; chunkId++) { + var getRequest = packages.get('package/' + packageName + '/' + chunkId); + getRequest.onsuccess = function(event) { + // If there's only 1 chunk, there's nothing to concatenate it with so we can just return it now + if (chunkCount == 1) { + callback(event.target.result); + } else { + chunksDone++; + totalSize += event.target.result.byteLength; + chunks.push(event.target.result); + if (chunksDone == chunkCount) { + if (chunksDone == 1) { + callback(event.target.result); + } else { + var tempTyped = new Uint8Array(totalSize); + var byteOffset = 0; + for (var chunkId in chunks) { + var buffer = chunks[chunkId]; + tempTyped.set(new Uint8Array(buffer), byteOffset); + byteOffset += buffer.byteLength; + buffer = undefined; + } + chunks = undefined; + callback(tempTyped.buffer); + tempTyped = undefined; + } + } + } + }; + getRequest.onerror = function(error) { + errback(error); + }; + } + } + """ + ) + + ret += r""" + function fetchRemotePackage(packageName, packageSize, callback, errback) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', packageName, true); + xhr.responseType = 'arraybuffer'; + xhr.onprogress = function(event) { + var url = packageName; + var size = packageSize; + if (event.total) size = event.total; + if (event.loaded) { + if (!xhr.addedTotal) { + xhr.addedTotal = true; + if (!Module.dataFileDownloads) Module.dataFileDownloads = {}; + Module.dataFileDownloads[url] = { + loaded: event.loaded, + total: size + }; + } else { + Module.dataFileDownloads[url].loaded = event.loaded; + } + var total = 0; + var loaded = 0; + var num = 0; + for (var download in Module.dataFileDownloads) { + var data = Module.dataFileDownloads[download]; + total += data.total; + loaded += data.loaded; + num++; + } + total = Math.ceil(total * Module.expectedDataFileDownloads/num); + if (Module['setStatus']) Module['setStatus']('Downloading data... (' + loaded + '/' + total + ')'); + } else if (!Module.dataFileDownloads) { + if (Module['setStatus']) Module['setStatus']('Downloading data...'); + } + }; + xhr.onerror = function(event) { + throw new Error("NetworkError for: " + packageName); + } + xhr.onload = function(event) { + if (xhr.status == 200 || xhr.status == 304 || xhr.status == 206 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 + var packageData = xhr.response; + callback(packageData); + } else { + throw new Error(xhr.statusText + " : " + xhr.responseURL); + } + }; + xhr.send(null); + }; + + function handleError(error) { + console.error('package error:', error); + }; + """ + + code += r""" + function processPackageData(arrayBuffer) { + Module.finishedDataFileDownloads++; + assert(arrayBuffer, 'Loading data file failed.'); + assert(arrayBuffer instanceof ArrayBuffer, 'bad input to processPackageData'); + var byteArray = new Uint8Array(arrayBuffer); + var curr; + %s + }; + Module['addRunDependency']('datafile_%s'); + """ % ( + use_data, + shared.JS.escape_for_js_string(data_target), + ) + # use basename because from the browser's point of view, + # we need to find the datafile in the same dir as the html file + + code += r""" + if (!Module.preloadResults) Module.preloadResults = {}; + """ + + if use_preload_cache: + code += r""" + function preloadFallback(error) { + console.error(error); + console.error('falling back to default preload behavior'); + fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, processPackageData, handleError); + }; + + openDatabase( + function(db) { + checkCachedPackage(db, PACKAGE_PATH + PACKAGE_NAME, + function(useCached, metadata) { + Module.preloadResults[PACKAGE_NAME] = {fromCache: useCached}; + if (useCached) { + fetchCachedPackage(db, PACKAGE_PATH + PACKAGE_NAME, metadata, processPackageData, preloadFallback); + } else { + fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, + function(packageData) { + cacheRemotePackage(db, PACKAGE_PATH + PACKAGE_NAME, packageData, {uuid:PACKAGE_UUID}, processPackageData, + function(error) { + console.error(error); + processPackageData(packageData); + }); + } + , preloadFallback); + } + } + , preloadFallback); + } + , preloadFallback); + + if (Module['setStatus']) Module['setStatus']('Downloading...'); + """ + else: + # Not using preload cache, so we might as well start the xhr ASAP, + # potentially before JS parsing of the main codebase if it's after us. + # Only tricky bit is the fetch is async, but also when runWithFS is called + # is async, so we handle both orderings. + ret += r""" + var fetchedCallback = null; + var fetched = Module['getPreloadedPackage'] ? Module['getPreloadedPackage'](REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE) : null; + + if (!fetched) fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, function(data) { + if (fetchedCallback) { + fetchedCallback(data); + fetchedCallback = null; + } else { + fetched = data; + } + }, handleError); + """ + + code += r""" + Module.preloadResults[PACKAGE_NAME] = {fromCache: false}; + if (fetched) { + processPackageData(fetched); + fetched = null; + } else { + fetchedCallback = processPackageData; + } + """ + + ret += """ + function runWithFS() { + """ + ret += code + ret += """ + } + if (Module['calledRun']) { + runWithFS(); + } else { + if (!Module['preRun']) Module['preRun'] = []; + Module["preRun"].push(runWithFS); // FS is not initialized yet, wait for it + } + """ + + if separate_metadata: + _metadata_template = """ + Module['removeRunDependency']('%(metadata_file)s'); + } + + function runMetaWithFS() { + Module['addRunDependency']('%(metadata_file)s'); + var REMOTE_METADATA_NAME = Module['locateFile'] ? Module['locateFile']('%(metadata_file)s', '') : '%(metadata_file)s'; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState === 4 && xhr.status === 200) { + loadPackage(JSON.parse(xhr.responseText)); + } + } + xhr.open('GET', REMOTE_METADATA_NAME, true); + xhr.overrideMimeType('application/json'); + xhr.send(null); + } + + if (Module['calledRun']) { + runMetaWithFS(); + } else { + if (!Module['preRun']) Module['preRun'] = []; + Module["preRun"].push(runMetaWithFS); + } + """ % { + "metadata_file": os.path.basename(jsoutput + ".metadata") + } + + else: + _metadata_template = """ + } + loadPackage(%s); + """ % json.dumps( + metadata + ) + + ret += ( + """%s + })(); + """ + % _metadata_template + ) + + if force or len(data_files): + if jsoutput is None: + print(ret) + else: + # Overwrite the old jsoutput file (if exists) only when its content + # differs from the current generated one, otherwise leave the file + # untouched preserving its old timestamp + if os.path.isfile(jsoutput): + with open(jsoutput) as f: + old = f.read() + if old != ret: + with open(jsoutput, "w") as f: + f.write(ret) + else: + with open(jsoutput, "w") as f: + f.write(ret) + if separate_metadata: + with open(jsoutput + ".metadata", "w") as f: + json.dump(metadata, f, separators=(",", ":")) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ports/wapy-wasm/main.c b/ports/wapy-wasm/main.c new file mode 100644 index 000000000..e429c353a --- /dev/null +++ b/ports/wapy-wasm/main.c @@ -0,0 +1,482 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include "../wapy/wapy.h" + + + +/* +LOGO: + https://hackernoon.com/screamin-speed-with-webassembly-b30fac90cd92 + https://www.gatherdigital.co.uk/news/2016/08/an-early-look-at-webassembly + +Python and web: + https://twitter.com/sfermigier/status/1193457847008403456 + https://www.mail-archive.com/web-sig@python.org/msg04347.html + https://bugs.python.org/issue40280 + https://discuss.python.org/t/python-in-the-browser/4248/6 + +OS ? : + https://github.com/S2E/PyKVM + https://github.com/plasma-umass/browsix + +NOGIL: + https://github.com/plasma-umass/snakefish + +THE USER POINT OF VIEW: + https://danluu.com/input-lag/ + + +BOOKMARKS: + +https://faster-cpython.readthedocs.io/implementations.html + +https://www.aosabook.org/en/500L/a-python-interpreter-written-in-python.html + +https://doc.pypy.org/en/latest/interpreter.html#introduction-and-overview + +https://github.com/stackless-dev/stackless/tree/master-slp/Stackless + +transpile/compile : + https://github.com/almarklein/wasmfun + https://github.com/pfalcon/pycopy-lib/tree/master/utokenize + https://github.com/pfalcon/ullvm_c + https://ppci.readthedocs.io/en/latest/reference/lang/python.html + https://pypi.org/project/py-ts-interfaces/ + https://github.com/numba/numba/issues/3284 + https://00f.net/2019/04/07/compiling-to-webassembly-with-llvm-and-clang/ + + https://github.com/pfalcon/awesome-python-compilers + + https://docs.python.org/3/library/inspect.html#retrieving-source-code + + https://01alchemist.com/projects/turboscript/playground/ + + + https://github.com/lark-parser/lark/blob/master/examples/python_parser.py + https://github.com/lark-parser/lark + + https://github.com/pyparsing/pyparsing + + https://wiki.python.org/moin/LanguageParsing + https://github.com/timothycrosley/jiphy + https://github.com/philhassey/tinypy + + + http://gambitscheme.org/wiki/index.php/Main_Page + + +jit: + http://llvmlite.pydata.org/en/latest/ + +interfacing: + + https://foss.heptapod.net/pypy/cffi + https://github.com/saghul/wasi-lab + +native use: + https://github.com/wasmerio/python-ext-wasm + https://pypi.org/project/wasmbind/ + +remote use: + https://pypi.org/project/jsii/ + + https://www.plynth.net/docs?id=async_await + +sixel support for remote + https://github.com/risacher/ttyx/issues/15 + + +ilyaigpetrov/ncurses-for-emscripten +ncurses 6.1 compiled by emscripten for usage in a browser. +It is compiled, loaded, but doesn't work! You are wanted to make it work! + https://github.com/ilyaigpetrov/ncurses-for-emscripten + + +------------- architecture / runtimes ------------------------- + +Stackless design? + https://github.com/micropython/micropython/issues/1036 + +Tasking for Emscripten/Wasm target #32532 + https://github.com/JuliaLang/julia/pull/32532/files#diff-15aaf3bb4e0956d73f314b03ddf85c8cR92 + +hyperdivision/async-wasm + https://github.com/hyperdivision/async-wasm/blob/master/index.js + + +wasm/gc/continuations: + https://github.com/WebAssembly/exception-handling/blob/master/proposals/Exceptions.md + https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html + https://github.com/emscripten-core/emscripten/issues/8268# + http://troubles.md/wasm-is-not-a-stack-machine/ + https://github.com/WebAssembly/design/issues/919#issuecomment-348000242 + + + https://bitbucket.org/pypy/stmgc/src/default/ + + https://github.com/WebAssembly/design/issues/1345#issuecomment-638228041 + + finalizers: thx stinos ! + https://github.com/micropython/micropython/issues/1878 + + WAPY implements C3: + https://github.com/WebAssembly/WASI/issues/276 + https://github.com/WebAssembly/design/issues/1252#issuecomment-461604032 + +Protothreads + http://dunkels.com/adam/pt/ + http://dunkels.com/adam/download/graham-pt.h + +C tools: + https://github.com/jamesmunns/bbqueue + https://github.com/willemt/bipbuffer/blob/master/bipbuffer.c + + A circular buffer written in C using Posix calls to create a contiguously mapped memory space. + https://github.com/willemt/cbuffer + + https://vstinner.readthedocs.io/c_language.html + + +ASYNC: + https://github.com/python-trio/trio/issues/79 + https://www.encode.io/httpx/async/ + https://www.python.org/dev/peps/pep-0492/ + https://www.python.org/dev/peps/pep-0525/ + https://www.pythonsheets.com/notes/python-asyncio.html + + https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ + + Re: [PATCH 09/13] aio: add support for async openat() + [Posted January 12, 2016 by corbet] + + https://lwn.net/Articles/671657/ + "In fact, if we do it well, we can go the other way, and try to +implement the nasty AIO interface on top of the generic "just do +things asynchronously". Linus + + +VM support: + https://github.com/cretz/asmble + + https://github.com/CraneStation/wasmtime + + https://nick.zoic.org/art/web-assembly-on-esp32-with-wasm-wamr/ + https://github.com/zephyrproject-rtos/zephyr/issues/21329 + + https://github.com/vshymanskyy/Wasm3_RGB_Lamp + + +https://snarky.ca/what-is-the-core-of-the-python-programming-language/ + + +pywasm: Support WASI(WebAssembly System Interface) + https://github.com/mohanson/pywasm/issues/25 + +webuse: + https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API + https://makitweb.com/how-to-detect-browser-window-active-or-not-javascript/ + https://codepen.io/jonathan/full/sxgJl + +repl: + Unicode Character 'RIGHT-TO-LEFT OVERRIDE' (U+202E) + +couldclose: + https://github.com/micropython/micropython/issues/3313 + +net layer: + https://github.com/moshest/p2p-index + https://rangermauve.hashbase.io/ + + + + https://github.com/iodide-project/pyodide/pull/606/files + https://github.com/pyhandle/piconumpy/tree/hpy + +*/ + + + + + +/* +CFLAGS="-Wfatal-errors -Wall -Wextra -Wunused -Werror -Wno-format-extra-args -Wno-format-zero-length\ + -Winit-self -Wimplicit -Wimplicit-int -Wmissing-include-dirs -Wswitch-default -Wswitch-enum\ + -Wunused-parameter -Wdouble-promotion -Wchkp -Wno-coverage-mismatch -Wstrict-overflow\ + -Wformat-nonliteral -Wformat-security -Wformat-signedness -Wnonnull -Wnonnull-compare\ + -Wnull-dereference -Wignored-qualifiers -Wignored-attributes\ + -Wmain -Wpedantic -Wmisleading-indentation -Wmissing-braces -Wmissing-include-dirs\ + -Wparentheses -Wsequence-point -Wshift-overflow=2 -Wswitch -Wswitch-default -Wswitch-bool\ + -Wsync-nand -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label\ + -Wunused-parameter -Wunused-result -Wunused-variable -Wunused-const-variable=2 -Wunused-value\ + -Wuninitialized -Winvalid-memory-model -Wmaybe-uninitialized -Wstrict-aliasing=3\ + -Wsuggest-attribute=pure -Wsuggest-attribute=const\ + -Wsuggest-attribute=noreturn -Wsuggest-attribute=format -Wmissing-format-attribute\ + -Wdiv-by-zero -Wunknown-pragmas -Wbool-compare -Wduplicated-cond\ + -Wtautological-compare -Wtrampolines -Wfloat-equal -Wfree-nonheap-object -Wunsafe-loop-optimizations\ + -Wpointer-arith -Wnonnull-compare -Wtype-limits -Wcomments -Wtrigraphs -Wundef\ + -Wendif-labels -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wclobbered\ + -Wconversion -Wdate-time -Wempty-body -Wjump-misses-init -Wsign-compare -Wsign-conversion\ + -Wfloat-conversion -Wsizeof-pointer-memaccess -Wsizeof-array-argument -Wpadded -Wredundant-decls\ + -Wnested-externs -Winline -Wbool-compare -Wno-int-to-pointer-cast -Winvalid-pch -Wlong-long\ + -Wvariadic-macros -Wvarargs -Wvector-operation-performance -Wvla -Wvolatile-register-var\ + -Wpointer-sign -Wstack-protector -Woverlength-strings -Wunsuffixed-float-constants\ + -Wno-designated-init -Whsa\ + -march=x86-64 -m64 -Wformat=2 -Warray-bounds=2 -Wstack-usage=120000 -Wstrict-overflow=5 -fmax-errors=5 -g\ + -std=c99 -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=700 -pedantic-errors" +*/ + +#define DBG 0 +#define DLOPEN 0 + + +#if !MICROPY_ENABLE_PYSTACK +#error "need MICROPY_ENABLE_PYSTACK (1) +#endif + +//static int SHOW_OS_LOOP=0; + +static int g_argc; +static char **g_argv; //[]; + + +/* ===================================================================================== + bad sync experiment with file access trying to help on + https://github.com/littlevgl/lvgl/issues/792 + + status: better than nothing, but wapy can/could/must use aio_* for that. +*/ + +#include "api/wasm_file_api.c" +#include "api/wasm_import_api.c" + +static int KPANIC = 0; + + + +char **copy_argv(int argc, char *argv[]) { + // calculate the contiguous argv buffer size + int length=0; + size_t ptr_args = argc + 1; + for (int i = 0; i < argc; i++) + { + length += (strlen(argv[i]) + 1); + } + char** new_argv = (char**)malloc((ptr_args) * sizeof(char*) + length); + // copy argv into the contiguous buffer + length = 0; + for (int i = 0; i < argc; i++) + { + new_argv[i] = &(((char*)new_argv)[(ptr_args * sizeof(char*)) + length]); + strcpy(new_argv[i], argv[i]); + length += (strlen(argv[i]) + 1); + } + // insert NULL terminating ptr at the end of the ptr array + new_argv[ptr_args-1] = NULL; + return (new_argv); +} + + + +#include "../wapy/upython.c" + +#include "vmsl/vmreg.h" + +#include "vmsl/vmreg.c" + +extern mp_obj_t fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +extern mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); +extern mp_obj_t gen_instance_iternext(mp_obj_t self_in); +extern int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); + +//extern mp_obj_t fun_bc_call_pre(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); +//extern mp_obj_t fun_bc_call_past(mp_code_state_t *code_state, mp_vm_return_kind_t vm_return_kind, mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); + + +#include + +#if MICROPY_PY_SYS_SETTRACE +#error "248:duplicate symbol: mp_type_code" +/* +wasm-ld: error: duplicate symbol: mp_type_code +>>> defined in libmicropython.a(profile.o) +>>> defined in libmicropython.a(builtinevex.o) +*/ +#include "py/profile.h" +#endif + +int handle_uncaught_exception(void); + +// entry point for implementing core vm parts in python, set via "embed" module +extern void pyv(mp_obj_t value); +extern mp_obj_t pycore(const char *fn); + +// entry point for line tracing +extern qstr trace_prev_file ; +extern size_t trace_prev_line; + +extern qstr trace_file ; +extern size_t trace_line; + +extern int trace_on; + +#define io_stdin i_main.shm_stdio + +// to use kb buffer space for scripts +// if (strlen(io_stdin)>=IO_KBD){ io_stdin[IO_KBD] = 0; +#define IO_CODE_DONE { io_stdin[0] = 0; } + +#if MICROPY_PY_THREAD_GIL && MICROPY_PY_THREAD_GIL_VM_DIVISOR +// This needs to be volatile and outside the VM loop so it persists across handling +// of any exceptions. Otherwise it's possible that the VM never gives up the GIL. +volatile int gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; +#endif + + +// can't be in loop body because of EM_ASM +void Py_Init() { + + wPy_Initialize(); + wPy_NewInterpreter(); + + EM_ASM( { + vm.aio.plink.MAXSIZE = $0; + vm.aio.plink.shm = $1; + vm.aio.plink.io_port_kbd = $2; + vm.aio.plink.MP_IO_SIZE = $3; + console.log("aio.plink.shm=" + vm.aio.plink.shm+" +" + vm.aio.plink.MAXSIZE); + console.log("aio.plink.io_port_kbd=" + vm.aio.plink.io_port_kbd+" +"+ vm.aio.plink.MP_IO_SIZE); + window.setTimeout( vm.script.init_repl, 1000 ); + }, IO_KBD, shm_ptr(), &io_stdin[IO_KBD], MP_IO_SIZE); + + IO_CODE_DONE; + +} + + +static bool def_PyRun_SimpleString_is_repl = false ; +static int async_loop = 1; +static int async_state; + + +extern int emscripten_GetProcAddress(const char * name); + + +#if MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE +static inline mp_map_elem_t *mp_map_cached_lookup(mp_map_t *map, qstr qst, uint8_t *idx_cache) { + size_t idx = *idx_cache; + mp_obj_t key = MP_OBJ_NEW_QSTR(qst); + mp_map_elem_t *elem = NULL; + if (idx < map->alloc && map->table[idx].key == key) { + elem = &map->table[idx]; + } else { + elem = mp_map_lookup(map, key, MP_MAP_LOOKUP); + if (elem != NULL) { + *idx_cache = (elem - &map->table[0]) & 0xff; + } + } + return elem; +} +#endif + +#include "../wapy/wapy.c" + + +extern int pyexec_repl_repl_restart(int ret); +extern int pyexec_friendly_repl_process_char(int c); + +int +main_iteration(void) { + if (VMOP <= VMOP_INIT) { + crash_point = &&VM_stackmess; + #include "vmsl/vmwarmup.c" + return 0; + } + + #define cc stdout + #include "../wapy/vmsl/vm_loop.c" + #undef cc + return 0; + +} // main_iteration + +/* +int +main_loop_warmup(void) { + #include "vmsl/vmwarmup.c" +} // main_loop_warmup +*/ + +//*************************************************************************************** + + + +#include + +void * +import(const char * LIB_NAME) { + void *lib_handle = dlopen(LIB_NAME, RTLD_NOW | RTLD_GLOBAL); + + if (!lib_handle) { + cdbg("\n\nDL ======> cannot load %s module\n\n\n", LIB_NAME); + } else { + cdbg("\n\nDL ======> %s module OK !!!!! \n\n\n", LIB_NAME); + } + return lib_handle; +} + + +int +main(int argc, char *argv[]) { + + g_argc = argc; + g_argv = copy_argv(argc, argv); + + //assert(emscripten_run_preload_plugins(LIB_NAME, NULL, NULL) == 0); + //void *lib_wapy = import( "libwapy.so"); + //void *lib_sdl2 = import( "libsdl2.so"); + + emscripten_set_main_loop( (em_callback_func)(void*)main_iteration, 0, 1); + return 0; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// + + diff --git a/ports/wapy-wasm/mod/ffi/ffi.c b/ports/wapy-wasm/mod/ffi/ffi.c new file mode 100644 index 000000000..b8509041e --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/ffi.c @@ -0,0 +1,218 @@ +#ifdef __EMSCRIPTEN__ + +#include "mod/ffi/ffi.h" +#include "ffi_common.h" +#include +#include + +#include "emscripten.h" + + +/* + + +https://groups.google.com/forum/#!topic/emscripten-discuss/cE3hUV3fDSw + +https://github.com/emscripten-core/emscripten/wiki/Linking + +https://github.com/dgym/cpython-emscripten/pull/1 + + +#FIXME: + +ffi_prep_closure_loc BROKEN +ffi_closure_alloc BROKEN + + +*/ + + + + +//===================================================================================================== +// following is a ripoff from https://github.com/brion/libffi/tree/emscripten-work +// + +ffi_status FFI_HIDDEN +ffi_prep_cif_machdep(ffi_cif *cif) +{ + return FFI_OK; +} + +void +ffi_call (ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue) +{ + EM_ASM_ ({ + var cif = $0; + var fn = $1; + var rvalue = $2; + var avalue = $3; + + var cif_abi = HEAPU32[cif >> 2]; + var cif_nargs = HEAPU32[(cif + 4) >> 2]; + var cif_arg_types = HEAPU32[(cif + 8) >> 2]; + var cif_rtype = HEAPU32[(cif + 12) >> 2]; + + // emscripten/wasm function pointers are indirected through a table, + // which is further subdivided by function signature -- each pointer + // must be added to a constant to find its real sig. This doesn't seem + // to be a strict wasm requirement, but is a leftover from asm.js + // which used separate arrays for each signature. + // + // This is neatly encapsulated into dynCall_* wrapper functions for us, + // which take a function pointer as first parameter and pass the rest on. + // + // Generate an emscripten call signature and look up the correct wrapper + // via name here... + var args = [fn]; + var sig = ""; + var sigFloat = Module['usingWasm'] ? "f" : "d"; + + var rtype = HEAPU16[(cif_rtype + 6 /* rtype->type*/ ) >> 1]; + //console.error('rtype is', rtype); + if (rtype === /* FFI_TYPE_VOID */ 0) { + sig = 'v'; + } else if (rtype === /* FFI_TYPE_INT */ 1 || + rtype === /* FFI_TYPE_UINT8 */ 5 || + rtype === /* FFI_TYPE_SINT8 */ 6 || + rtype === /* FFI_TYPE_UINT16 */ 7 || + rtype === /* FFI_TYPE_SINT16 */ 8 || + rtype === /* FFI_TYPE_UINT32 */ 9 || + rtype === /* FFI_TYPE_SINT32 */ 10 || + rtype === /* FFI_TYPE_POINTER */ 14) { + sig = 'i'; + } else if (rtype === /* FFI_TYPE_FLOAT */ 2) { + sig = sigFloat; + } else if (rtype === /* FFI_TYPE_DOUBLE */ 3 || + rtype === /* FFI_TYPE_LONGDOUBLE */ 4) { + sig = 'd'; + } else if (rtype === /* FFI_TYPE_UINT64 */ 11 || + rtype === /* FFI_TYPE_SINT64 */ 12) { + // Warning: returns a truncated 32-bit integer directly. + // High bits are in $tempRet0 + sig = 'j'; + } else if (rtype === /* FFI_TYPE_STRUCT */ 13) { + throw new Error('struct ret marshalling nyi'); + } else if (rtype === /* FFI_TYPE_COMPLEX */ 15) { + throw new Error('complex ret marshalling nyi'); + } else { + throw new Error('Unexpected rtype ' + rtype); + } + + for (var i = 0; i < cif_nargs; i++) { + var ptr = HEAPU32[(avalue >> 2) + i]; + + var arg_type = HEAPU32[(cif_arg_types >> 2) + i]; + var typ = HEAPU16[(arg_type + 6) >> 1]; + + if (typ === /* FFI_TYPE_INT*/ 1 || typ === /* FFI_TYPE_SINT32 */ 10) { + args.push(HEAP32[ptr >> 2]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_FLOAT */ 2) { + args.push(HEAPF32[ptr >> 2]); + sig += sigFloat; + } else if (typ === /* FFI_TYPE_DOUBLE */ 3 || typ === /* FFI_TYPE_LONGDOUBLE */ 4) { + args.push(HEAPF64[ptr >> 3]); + sig += 'd'; + } else if (typ === /* FFI_TYPE_UINT8*/ 5) { + args.push(HEAPU8[ptr]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_SINT8 */ 6) { + args.push(HEAP8[ptr]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_UINT16 */ 7) { + args.push(HEAPU16[ptr >> 1]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_SINT16 */ 8) { + args.push(HEAP16[ptr >> 1]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_UINT32 */ 9 || typ === /* FFI_TYPE_POINTER */ 14) { + args.push(HEAPU32[ptr >> 2]); + sig += 'i'; + } else if (typ === /* FFI_TYPE_UINT64 */ 11 || typ === /* FFI_TYPE_SINT64 */ 12) { + // LEGALIZE_JS_FFI mode splits i64 (j) into two i32 args + // for compatibility with JavaScript's f64-based numbers. + args.push(HEAPU32[ptr >> 2]); + args.push(HEAPU32[(ptr + 4) >> 2]); + sig += 'j'; + } else if (typ === /* FFI_TYPE_STRUCT */ 13) { + throw new Error('struct marshalling nyi'); + } else if (typ === /* FFI_TYPE_COMPLEX */ 15) { + throw new Error('complex marshalling nyi'); + } else { + throw new Error('Unexpected type ' + typ); + } + } + + //console.error('fn is',fn); + //console.error('sig is',sig); + var func = Module['dynCall_' + sig]; + //console.error('func is', func); + //console.error('args is', args); + if (func) { + var result = func.apply(null, args); + } else { + console.error('fn is', fn); + console.error('sig is', sig); + console.error('args is', args); + for (var x in Module) { + console.error('-- ' + x); + } + throw new Error('invalid function pointer in ffi_call'); + } + //console.error('result is',result); + + if (rtype === 0) { + // void + } else if (rtype === 1 || rtype === 9 || rtype === 10 || rtype === 14) { + HEAP32[rvalue >> 2] = result; + } else if (rtype === 2) { + HEAPF32[rvalue >> 2] = result; + } else if (rtype === 3 || rtype === 4) { + HEAPF64[rvalue >> 3] = result; + } else if (rtype === 5 || rtype === 6) { + HEAP8[rvalue] = result; + } else if (rtype === 7 || rtype === 8) { + HEAP16[rvalue >> 1] = result; + } else if (rtype === 11 || rtype === 12) { + // Warning: returns a truncated 32-bit integer directly. + // High bits are in $tempRet0 + HEAP32[rvalue >> 2] = result; + HEAP32[(rvalue + 4) >> 2] = Module.getTempRet0(); + } else if (rtype === 13) { + throw new Error('struct ret marshalling nyi'); + } else if (rtype === 15) { + throw new Error('complex ret marshalling nyi'); + } else { + throw new Error('Unexpected rtype ' + rtype); + } + }, cif, fn, rvalue, avalue); +} +#else + +#include "ffi.h" + +#endif + + +ffi_status +ffi_prep_closure_loc (ffi_closure* closure, + ffi_cif* cif, + void (*fun)(ffi_cif*, void*, void**, void*), + void *user_data, + void *codeloc) +{ + closure->cif = cif; + closure->fun = fun; + closure->user_data = user_data; + return FFI_OK; +} + + + +void * +ffi_closure_alloc (size_t size, void **code) +{ + return 0; +} + diff --git a/ports/wapy-wasm/mod/ffi/ffi.h b/ports/wapy-wasm/mod/ffi/ffi.h new file mode 100644 index 000000000..580430539 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/ffi.h @@ -0,0 +1,481 @@ +/* -----------------------------------------------------------------*-C-*- + libffi 3.3-rc0 - Copyright (c) 2011, 2014 Anthony Green + - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. + + 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. + + ----------------------------------------------------------------------- */ + +/* ------------------------------------------------------------------- + Most of the API is documented in doc/libffi.texi. + + The raw API is designed to bypass some of the argument packing and + unpacking on architectures for which it can be avoided. Routines + are provided to emulate the raw API if the underlying platform + doesn't allow faster implementation. + + More details on the raw API can be found in: + + http://gcc.gnu.org/ml/java/1999-q3/msg00138.html + + and + + http://gcc.gnu.org/ml/java/1999-q3/msg00174.html + -------------------------------------------------------------------- */ + +#ifndef LIBFFI_H +#define LIBFFI_H + + + +/* Specify which architecture libffi is configured for. */ +#ifndef X86 +#define X86 +#endif + +// Begin : ============================================================================ + +// ?? +typedef unsigned long ffi_arg; +typedef signed long ffi_sarg; + +typedef enum ffi_abi { + FFI_FIRST_ABI = 0, + FFI_WASM32, // "raw", no structures or varargs + FFI_WASM32_EMSCRIPTEN, // structures, varargs, and split 64-bit params + FFI_LAST_ABI, +#ifdef EMSCRIPTEN + FFI_DEFAULT_ABI = FFI_WASM32_EMSCRIPTEN +#else + FFI_DEFAULT_ABI = FFI_WASM32 +#endif +} ffi_abi; + +#define FFI_CLOSURES 1 //PMPP +#define FFI_GO_CLOSURES 0 +#define FFI_TRAMPOLINE_SIZE 24 +#define FFI_NATIVE_RAW_API 0 + +// End : ============================================================================= + + + + +#ifndef LIBFFI_ASM + +#include +#include + +/* LONG_LONG_MAX is not always defined (not if STRICT_ANSI, for example). + But we can find it either under the correct ANSI name, or under GNU + C's internal name. */ + +#define FFI_64_BIT_MAX 9223372036854775807 + +#ifdef LONG_LONG_MAX + #define FFI_LONG_LONG_MAX LONG_LONG_MAX +#else + # ifdef LLONG_MAX + # define FFI_LONG_LONG_MAX LLONG_MAX + # ifdef _AIX52 /* or newer has C99 LLONG_MAX */ + # undef FFI_64_BIT_MAX + # define FFI_64_BIT_MAX 9223372036854775807LL + # endif /* _AIX52 or newer */ + # else + # ifdef __GNUC__ + # define FFI_LONG_LONG_MAX __LONG_LONG_MAX__ + # endif + # endif +#endif + +/* The closure code assumes that this works on pointers, i.e. a size_t can hold a pointer. */ + +typedef struct _ffi_type +{ + size_t size; + unsigned short alignment; + unsigned short type; + struct _ffi_type **elements; +} ffi_type; + +# define FFI_API + +/* The externally visible type declarations also need the MSVC DLL + decorations, or they will not be exported from the object file. */ +#if defined LIBFFI_HIDE_BASIC_TYPES +# define FFI_EXTERN FFI_API +#else +# define FFI_EXTERN extern FFI_API +#endif + +#ifndef LIBFFI_HIDE_BASIC_TYPES +#if SCHAR_MAX == 127 +# define ffi_type_uchar ffi_type_uint8 +# define ffi_type_schar ffi_type_sint8 +#else + #error "char size not supported" +#endif + +#if SHRT_MAX == 32767 +# define ffi_type_ushort ffi_type_uint16 +# define ffi_type_sshort ffi_type_sint16 +#elif SHRT_MAX == 2147483647 +# define ffi_type_ushort ffi_type_uint32 +# define ffi_type_sshort ffi_type_sint32 +#else + #error "short size not supported" +#endif + +#if INT_MAX == 32767 +# define ffi_type_uint ffi_type_uint16 +# define ffi_type_sint ffi_type_sint16 +#elif INT_MAX == 2147483647 +# define ffi_type_uint ffi_type_uint32 +# define ffi_type_sint ffi_type_sint32 +#elif INT_MAX == 9223372036854775807 +# define ffi_type_uint ffi_type_uint64 +# define ffi_type_sint ffi_type_sint64 +#else + #error "int size not supported" +#endif + +#if LONG_MAX == 2147483647 +# if FFI_LONG_LONG_MAX != FFI_64_BIT_MAX + #error "no 64-bit data type supported" +# endif +#elif LONG_MAX != FFI_64_BIT_MAX + #error "long size not supported" +#endif + +#if LONG_MAX == 2147483647 +# define ffi_type_ulong ffi_type_uint32 +# define ffi_type_slong ffi_type_sint32 +#elif LONG_MAX == FFI_64_BIT_MAX +# define ffi_type_ulong ffi_type_uint64 +# define ffi_type_slong ffi_type_sint64 +#else + #error "long size not supported" +#endif + +/* These are defined in ffi_types.c. */ +FFI_EXTERN ffi_type ffi_type_void; +FFI_EXTERN ffi_type ffi_type_uint8; +FFI_EXTERN ffi_type ffi_type_sint8; +FFI_EXTERN ffi_type ffi_type_uint16; +FFI_EXTERN ffi_type ffi_type_sint16; +FFI_EXTERN ffi_type ffi_type_uint32; +FFI_EXTERN ffi_type ffi_type_sint32; +FFI_EXTERN ffi_type ffi_type_uint64; +FFI_EXTERN ffi_type ffi_type_sint64; +FFI_EXTERN ffi_type ffi_type_float; +FFI_EXTERN ffi_type ffi_type_double; +FFI_EXTERN ffi_type ffi_type_pointer; + +#define ffi_type_longdouble ffi_type_double + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_EXTERN ffi_type ffi_type_complex_float; +FFI_EXTERN ffi_type ffi_type_complex_double; +#if 0 +FFI_EXTERN ffi_type ffi_type_complex_longdouble; +#else +#define ffi_type_complex_longdouble ffi_type_complex_double +#endif +#endif +#endif /* LIBFFI_HIDE_BASIC_TYPES */ + +typedef enum { + FFI_OK = 0, + FFI_BAD_TYPEDEF, + FFI_BAD_ABI +} ffi_status; + +typedef struct { + ffi_abi abi; + unsigned nargs; + ffi_type **arg_types; + ffi_type *rtype; + unsigned bytes; + unsigned flags; +#ifdef FFI_EXTRA_CIF_FIELDS + FFI_EXTRA_CIF_FIELDS; +#endif +} ffi_cif; + +/* ---- Definitions for the raw API -------------------------------------- */ + +#ifndef FFI_SIZEOF_ARG +# if LONG_MAX == 2147483647 +# define FFI_SIZEOF_ARG 4 +# elif LONG_MAX == FFI_64_BIT_MAX +# define FFI_SIZEOF_ARG 8 +# endif +#endif + +#ifndef FFI_SIZEOF_JAVA_RAW +# define FFI_SIZEOF_JAVA_RAW FFI_SIZEOF_ARG +#endif + +typedef union { + ffi_sarg sint; + ffi_arg uint; + float flt; + char data[FFI_SIZEOF_ARG]; + void* ptr; +} ffi_raw; + +#if FFI_SIZEOF_JAVA_RAW == 4 && FFI_SIZEOF_ARG == 8 +/* This is a special case for mips64/n32 ABI (and perhaps others) where + sizeof(void *) is 4 and FFI_SIZEOF_ARG is 8. */ +typedef union { + signed int sint; + unsigned int uint; + float flt; + char data[FFI_SIZEOF_JAVA_RAW]; + void* ptr; +} ffi_java_raw; +#else +typedef ffi_raw ffi_java_raw; +#endif + + +FFI_API +void ffi_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_raw *avalue); + +FFI_API void ffi_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_raw *raw); +FFI_API void ffi_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args); +FFI_API size_t ffi_raw_size (ffi_cif *cif); + +/* This is analogous to the raw API, except it uses Java parameter + packing, even on 64-bit machines. I.e. on 64-bit machines longs + and doubles are followed by an empty 64-bit word. */ + +FFI_API +void ffi_java_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_java_raw *avalue); + +FFI_API +void ffi_java_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_java_raw *raw); +FFI_API +void ffi_java_raw_to_ptrarray (ffi_cif *cif, ffi_java_raw *raw, void **args); +FFI_API +size_t ffi_java_raw_size (ffi_cif *cif); + +/* ---- Definitions for closures ----------------------------------------- */ + +#if FFI_CLOSURES + +#ifdef _MSC_VER +__declspec(align(8)) +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + void (*fun)(ffi_cif*,void*,void**,void*); + void *user_data; +} ffi_closure +#ifdef __GNUC__ + __attribute__((aligned (8))) +#endif + ; + +#ifndef __GNUC__ +# ifdef __sgi +# pragma pack 0 +# endif +#endif + +FFI_API void *ffi_closure_alloc (size_t size, void **code); +FFI_API void ffi_closure_free (void *); + +FFI_API ffi_status +ffi_prep_closure (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data) +#if defined(__GNUC__) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 405) + __attribute__((deprecated ("use ffi_prep_closure_loc instead"))) +#elif defined(__GNUC__) && __GNUC__ >= 3 + __attribute__((deprecated)) +#endif + ; + +FFI_API ffi_status +ffi_prep_closure_loc (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data, + void*codeloc); + +#ifdef __sgi +# pragma pack 8 +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* If this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_raw*,void*); + void *user_data; + +} ffi_raw_closure; + +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* If this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the translation, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*); + void *user_data; + +} ffi_java_raw_closure; + +FFI_API ffi_status +ffi_prep_raw_closure (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data); + +FFI_API ffi_status +ffi_prep_raw_closure_loc (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data, + void *codeloc); + +FFI_API ffi_status +ffi_prep_java_raw_closure (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data); + +FFI_API ffi_status +ffi_prep_java_raw_closure_loc (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data, + void *codeloc); + +#endif /* FFI_CLOSURES */ + + +/* ---- Public interface definition -------------------------------------- */ + +FFI_API +ffi_status ffi_prep_cif(ffi_cif *cif, + ffi_abi abi, + unsigned int nargs, + ffi_type *rtype, + ffi_type **atypes); + +FFI_API +ffi_status ffi_prep_cif_var(ffi_cif *cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +FFI_API +void ffi_call(ffi_cif *cif, + void (*fn)(void), + void *rvalue, + void **avalue); + +FFI_API +ffi_status ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, + size_t *offsets); + +/* Useful for eliminating compiler warnings. */ +#define FFI_FN(f) ((void (*)(void))f) + +/* ---- Definitions shared with assembly code ---------------------------- */ + +#endif + +/* If these change, update src/mips/ffitarget.h. */ +#define FFI_TYPE_VOID 0 +#define FFI_TYPE_INT 1 +#define FFI_TYPE_FLOAT 2 +#define FFI_TYPE_DOUBLE 3 +#if 0 +#define FFI_TYPE_LONGDOUBLE 4 +#else +#define FFI_TYPE_LONGDOUBLE FFI_TYPE_DOUBLE +#endif +#define FFI_TYPE_UINT8 5 +#define FFI_TYPE_SINT8 6 +#define FFI_TYPE_UINT16 7 +#define FFI_TYPE_SINT16 8 +#define FFI_TYPE_UINT32 9 +#define FFI_TYPE_SINT32 10 +#define FFI_TYPE_UINT64 11 +#define FFI_TYPE_SINT64 12 +#define FFI_TYPE_STRUCT 13 +#define FFI_TYPE_POINTER 14 +#define FFI_TYPE_COMPLEX 15 + +/* This should always refer to the last type code (for sanity checks). */ +#define FFI_TYPE_LAST FFI_TYPE_COMPLEX + + +#endif diff --git a/ports/wapy-wasm/mod/ffi/ffi_common.h b/ports/wapy-wasm/mod/ffi/ffi_common.h new file mode 100644 index 000000000..727f39e65 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/ffi_common.h @@ -0,0 +1,149 @@ +/* ----------------------------------------------------------------------- + ffi_common.h - Copyright (C) 2011, 2012, 2013 Anthony Green + Copyright (C) 2007 Free Software Foundation, Inc + Copyright (c) 1996 Red Hat, Inc. + + Common internal definitions and macros. Only necessary for building + libffi. + ----------------------------------------------------------------------- */ + +#ifndef FFI_COMMON_H +#define FFI_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Do not move this. Some versions of AIX are very picky about where + this is positioned. */ +#ifdef __GNUC__ +# if HAVE_ALLOCA_H +# include +# else + /* mingw64 defines this already in malloc.h. */ +# ifndef alloca +# define alloca __builtin_alloca +# endif +# endif +# define MAYBE_UNUSED __attribute__((__unused__)) +#else +# define MAYBE_UNUSED +# if HAVE_ALLOCA_H +# include +# else +# ifdef _AIX +# pragma alloca +# else +# ifndef alloca /* predefined by HP cc +Olibcalls */ +# ifdef _MSC_VER +# define alloca _alloca +# else +char *alloca (); +# endif +# endif +# endif +# endif +#endif + +/* Check for the existence of memcpy. */ +#if STDC_HEADERS +# include +#else +# ifndef HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#if defined(FFI_DEBUG) +#include +#endif + +#ifdef FFI_DEBUG +void ffi_assert(char *expr, char *file, int line); +void ffi_stop_here(void); +void ffi_type_test(ffi_type *a, char *file, int line); + +#define FFI_ASSERT(x) ((x) ? (void)0 : ffi_assert(#x, __FILE__,__LINE__)) +#define FFI_ASSERT_AT(x, f, l) ((x) ? 0 : ffi_assert(#x, (f), (l))) +#define FFI_ASSERT_VALID_TYPE(x) ffi_type_test (x, __FILE__, __LINE__) +#else +#define FFI_ASSERT(x) +#define FFI_ASSERT_AT(x, f, l) +#define FFI_ASSERT_VALID_TYPE(x) +#endif + +/* v cast to size_t and aligned up to a multiple of a */ +#define FFI_ALIGN(v, a) (((((size_t) (v))-1) | ((a)-1))+1) +/* v cast to size_t and aligned down to a multiple of a */ +#define FFI_ALIGN_DOWN(v, a) (((size_t) (v)) & -a) + +/* Perform machine dependent cif processing */ +ffi_status ffi_prep_cif_machdep(ffi_cif *cif); +ffi_status ffi_prep_cif_machdep_var(ffi_cif *cif, + unsigned int nfixedargs, unsigned int ntotalargs); + + +#if HAVE_LONG_DOUBLE_VARIANT +/* Used to adjust size/alignment of ffi types. */ +void ffi_prep_types (ffi_abi abi); +#endif + +/* Used internally, but overridden by some architectures */ +ffi_status ffi_prep_cif_core(ffi_cif *cif, + ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +/* Extended cif, used in callback from assembly routine */ +typedef struct +{ + ffi_cif *cif; + void *rvalue; + void **avalue; +} extended_cif; + +/* Terse sized type definitions. */ +#if defined(_MSC_VER) || defined(__sgi) || defined(__SUNPRO_C) +typedef unsigned char UINT8; +typedef signed char SINT8; +typedef unsigned short UINT16; +typedef signed short SINT16; +typedef unsigned int UINT32; +typedef signed int SINT32; +# ifdef _MSC_VER +typedef unsigned __int64 UINT64; +typedef signed __int64 SINT64; +# else +# include +typedef uint64_t UINT64; +typedef int64_t SINT64; +# endif +#else +typedef unsigned int UINT8 __attribute__((__mode__(__QI__))); +typedef signed int SINT8 __attribute__((__mode__(__QI__))); +typedef unsigned int UINT16 __attribute__((__mode__(__HI__))); +typedef signed int SINT16 __attribute__((__mode__(__HI__))); +typedef unsigned int UINT32 __attribute__((__mode__(__SI__))); +typedef signed int SINT32 __attribute__((__mode__(__SI__))); +typedef unsigned int UINT64 __attribute__((__mode__(__DI__))); +typedef signed int SINT64 __attribute__((__mode__(__DI__))); +#endif + +typedef float FLOAT32; + +#ifndef __GNUC__ +#define __builtin_expect(x, expected_value) (x) +#endif +#define LIKELY(x) __builtin_expect(!!(x),1) +#define UNLIKELY(x) __builtin_expect((x)!=0,0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/wapy-wasm/mod/ffi/fficonfig.h b/ports/wapy-wasm/mod/ffi/fficonfig.h new file mode 100644 index 000000000..13a80dd63 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/fficonfig.h @@ -0,0 +1,215 @@ +/* fficonfig.h. Generated from fficonfig.h.in by configure. */ +/* fficonfig.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP + systems. This function is required for `alloca.c' support on those systems. + */ +/* #undef CRAY_STACKSEG_END */ + +/* Define to 1 if using `alloca.c'. */ +/* #undef C_ALLOCA */ + +/* Define to the flags needed for the .section .eh_frame directive. */ +#define EH_FRAME_FLAGS "aw" + +/* Define this if you want extra debugging. */ +/* #undef FFI_DEBUG */ + +/* Cannot use PROT_EXEC on this target, so, we revert to alternative means */ +/* #undef FFI_EXEC_TRAMPOLINE_TABLE */ + +/* Define this if you want to enable pax emulated trampolines */ +/* #undef FFI_MMAP_EXEC_EMUTRAMP_PAX */ + +/* Cannot use malloc on this target, so, we revert to alternative means */ +/* #undef FFI_MMAP_EXEC_WRIT */ + +/* Define this if you do not want support for the raw API. */ +/* #undef FFI_NO_RAW_API */ + +/* Define this if you do not want support for aggregate types. */ +/* #undef FFI_NO_STRUCTS */ + +/* Define to 1 if you have `alloca', as a function or macro. */ +#define HAVE_ALLOCA 1 + +/* Define to 1 if you have and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define if your assembler supports .cfi_* directives. */ +#define HAVE_AS_CFI_PSEUDO_OP 1 + +/* Define if your assembler supports .register. */ +/* #undef HAVE_AS_REGISTER_PSEUDO_OP */ + +/* Define if the compiler uses zarch features. */ +/* #undef HAVE_AS_S390_ZARCH */ + +/* Define if your assembler and linker support unaligned PC relative relocs. + */ +/* #undef HAVE_AS_SPARC_UA_PCREL */ + +/* Define if your assembler supports unwind section type. */ +/* #undef HAVE_AS_X86_64_UNWIND_SECTION_TYPE */ + +/* Define if your assembler supports PC relative relocs. */ +/* #undef HAVE_AS_X86_PCREL */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if __attribute__((visibility("hidden"))) is supported. */ +/* #undef HAVE_HIDDEN_VISIBILITY_ATTRIBUTE */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if you have the long double type and it is bigger than a double */ +/* #undef HAVE_LONG_DOUBLE */ + +/* Define if you support more than one size of the long double type */ +/* #undef HAVE_LONG_DOUBLE_VARIANT */ + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkostemp' function. */ +#define HAVE_MKOSTEMP 1 + +/* Define to 1 if you have the `mmap' function. */ +#define HAVE_MMAP 1 + +/* Define if mmap with MAP_ANON(YMOUS) works. */ +#define HAVE_MMAP_ANON 1 + +/* Define if mmap of /dev/zero works. */ +#define HAVE_MMAP_DEV_ZERO 1 + +/* Define if read-only mmap of a plain file works. */ +#define HAVE_MMAP_FILE 1 + +/* Define if .eh_frame sections should be read-only. */ +/* #undef HAVE_RO_EH_FRAME */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if GNU symbol versioning is used for libatomic. */ +/* #undef LIBFFI_GNU_SYMBOL_VERSIONING */ + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "libffi" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "http://github.com/libffi/libffi/issues" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libffi" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libffi 3.3-rc0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libffi" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "3.3-rc0" + +/* The size of `double', as computed by sizeof. */ +#define SIZEOF_DOUBLE 8 + +/* The size of `long double', as computed by sizeof. */ +#define SIZEOF_LONG_DOUBLE 8 + +/* The size of `size_t', as computed by sizeof. */ +#define SIZEOF_SIZE_T 4 + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at runtime. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown */ +/* #undef STACK_DIRECTION */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if symbols are underscored. */ +/* #undef SYMBOL_UNDERSCORE */ + +/* Define this if you are using Purify and want to suppress spurious messages. + */ +/* #undef USING_PURIFY */ + +/* Version number of package */ +#define VERSION "3.3-rc0" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + + +#ifdef HAVE_HIDDEN_VISIBILITY_ATTRIBUTE +#ifdef LIBFFI_ASM +#ifdef __APPLE__ +#define FFI_HIDDEN(name) .private_extern name +#else +#define FFI_HIDDEN(name) .hidden name +#endif +#else +#define FFI_HIDDEN __attribute__ ((visibility ("hidden"))) +#endif +#else +#ifdef LIBFFI_ASM +#define FFI_HIDDEN(name) +#else +#define FFI_HIDDEN +#endif +#endif + diff --git a/ports/wapy-wasm/mod/ffi/prep_cif.c b/ports/wapy-wasm/mod/ffi/prep_cif.c new file mode 100644 index 000000000..9e1197000 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/prep_cif.c @@ -0,0 +1,261 @@ +/* ----------------------------------------------------------------------- + prep_cif.c - Copyright (c) 2011, 2012 Anthony Green + Copyright (c) 1996, 1998, 2007 Red Hat, Inc. + + 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 + +/* Round up to FFI_SIZEOF_ARG. */ + +#define STACK_ARG_SIZE(x) FFI_ALIGN(x, FFI_SIZEOF_ARG) + +/* Perform machine independent initialization of aggregate type + specifications. */ + +static ffi_status initialize_aggregate(ffi_type *arg, size_t *offsets) +{ + ffi_type **ptr; + + if (UNLIKELY(arg == NULL || arg->elements == NULL)) + return FFI_BAD_TYPEDEF; + + arg->size = 0; + arg->alignment = 0; + + ptr = &(arg->elements[0]); + + if (UNLIKELY(ptr == 0)) + return FFI_BAD_TYPEDEF; + + while ((*ptr) != NULL) + { + if (UNLIKELY(((*ptr)->size == 0) + && (initialize_aggregate((*ptr), NULL) != FFI_OK))) + return FFI_BAD_TYPEDEF; + + /* Perform a sanity check on the argument type */ + FFI_ASSERT_VALID_TYPE(*ptr); + + arg->size = FFI_ALIGN(arg->size, (*ptr)->alignment); + if (offsets) + *offsets++ = arg->size; + arg->size += (*ptr)->size; + + arg->alignment = (arg->alignment > (*ptr)->alignment) ? + arg->alignment : (*ptr)->alignment; + + ptr++; + } + + /* Structure size includes tail padding. This is important for + structures that fit in one register on ABIs like the PowerPC64 + Linux ABI that right justify small structs in a register. + It's also needed for nested structure layout, for example + struct A { long a; char b; }; struct B { struct A x; char y; }; + should find y at an offset of 2*sizeof(long) and result in a + total size of 3*sizeof(long). */ + arg->size = FFI_ALIGN (arg->size, arg->alignment); + + /* On some targets, the ABI defines that structures have an additional + alignment beyond the "natural" one based on their elements. */ +#ifdef FFI_AGGREGATE_ALIGNMENT + if (FFI_AGGREGATE_ALIGNMENT > arg->alignment) + arg->alignment = FFI_AGGREGATE_ALIGNMENT; +#endif + + if (arg->size == 0) + return FFI_BAD_TYPEDEF; + else + return FFI_OK; +} + +#ifndef __CRIS__ +/* The CRIS ABI specifies structure elements to have byte + alignment only, so it completely overrides this functions, + which assumes "natural" alignment and padding. */ + +/* Perform machine independent ffi_cif preparation, then call + machine dependent routine. */ + +/* For non variadic functions isvariadic should be 0 and + nfixedargs==ntotalargs. + + For variadic calls, isvariadic should be 1 and nfixedargs + and ntotalargs set as appropriate. nfixedargs must always be >=1 */ + + +ffi_status FFI_HIDDEN ffi_prep_cif_core(ffi_cif *cif, ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, ffi_type **atypes) +{ + unsigned bytes = 0; + unsigned int i; + ffi_type **ptr; + + FFI_ASSERT(cif != NULL); + FFI_ASSERT((!isvariadic) || (nfixedargs >= 1)); + FFI_ASSERT(nfixedargs <= ntotalargs); + + if (! (abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) + return FFI_BAD_ABI; + + cif->abi = abi; + cif->arg_types = atypes; + cif->nargs = ntotalargs; + cif->rtype = rtype; + + cif->flags = 0; + +#if HAVE_LONG_DOUBLE_VARIANT + ffi_prep_types (abi); +#endif + + /* Initialize the return type if necessary */ + if ((cif->rtype->size == 0) + && (initialize_aggregate(cif->rtype, NULL) != FFI_OK)) + return FFI_BAD_TYPEDEF; + +#ifndef FFI_TARGET_HAS_COMPLEX_TYPE + if (rtype->type == FFI_TYPE_COMPLEX) + abort(); +#endif + /* Perform a sanity check on the return type */ + FFI_ASSERT_VALID_TYPE(cif->rtype); + + /* x86, x86-64 and s390 stack space allocation is handled in prep_machdep. */ +#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION + /* Make space for the return structure pointer */ + if (cif->rtype->type == FFI_TYPE_STRUCT +#ifdef TILE + && (cif->rtype->size > 10 * FFI_SIZEOF_ARG) +#endif +#ifdef XTENSA + && (cif->rtype->size > 16) +#endif +#ifdef NIOS2 + && (cif->rtype->size > 8) +#endif + ) + bytes = STACK_ARG_SIZE(sizeof(void*)); +#endif + + for (ptr = cif->arg_types, i = cif->nargs; i > 0; i--, ptr++) + { + + /* Initialize any uninitialized aggregate type definitions */ + if (((*ptr)->size == 0) + && (initialize_aggregate((*ptr), NULL) != FFI_OK)) + return FFI_BAD_TYPEDEF; + +#ifndef FFI_TARGET_HAS_COMPLEX_TYPE + if ((*ptr)->type == FFI_TYPE_COMPLEX) + abort(); +#endif + /* Perform a sanity check on the argument type, do this + check after the initialization. */ + FFI_ASSERT_VALID_TYPE(*ptr); + +#if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION + { + /* Add any padding if necessary */ + if (((*ptr)->alignment - 1) & bytes) + bytes = (unsigned)FFI_ALIGN(bytes, (*ptr)->alignment); + +#ifdef TILE + if (bytes < 10 * FFI_SIZEOF_ARG && + bytes + STACK_ARG_SIZE((*ptr)->size) > 10 * FFI_SIZEOF_ARG) + { + /* An argument is never split between the 10 parameter + registers and the stack. */ + bytes = 10 * FFI_SIZEOF_ARG; + } +#endif +#ifdef XTENSA + if (bytes <= 6*4 && bytes + STACK_ARG_SIZE((*ptr)->size) > 6*4) + bytes = 6*4; +#endif + + bytes += STACK_ARG_SIZE((*ptr)->size); + } +#endif + } + + cif->bytes = bytes; + + /* Perform machine dependent cif processing */ +#ifdef FFI_TARGET_SPECIFIC_VARIADIC + if (isvariadic) + return ffi_prep_cif_machdep_var(cif, nfixedargs, ntotalargs); +#endif + + return ffi_prep_cif_machdep(cif); +} +#endif /* not __CRIS__ */ + +ffi_status ffi_prep_cif(ffi_cif *cif, ffi_abi abi, unsigned int nargs, + ffi_type *rtype, ffi_type **atypes) +{ + return ffi_prep_cif_core(cif, abi, 0, nargs, nargs, rtype, atypes); +} + +ffi_status ffi_prep_cif_var(ffi_cif *cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes) +{ + return ffi_prep_cif_core(cif, abi, 1, nfixedargs, ntotalargs, rtype, atypes); +} + +#if FFI_CLOSURES + +ffi_status +ffi_prep_closure (ffi_closure* closure, + ffi_cif* cif, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data) +{ + return ffi_prep_closure_loc (closure, cif, fun, user_data, closure); +} + +#endif + +ffi_status +ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, size_t *offsets) +{ + if (! (abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) + return FFI_BAD_ABI; + if (struct_type->type != FFI_TYPE_STRUCT) + return FFI_BAD_TYPEDEF; + +#if HAVE_LONG_DOUBLE_VARIANT + ffi_prep_types (abi); +#endif + + return initialize_aggregate(struct_type, offsets); +} diff --git a/ports/wapy-wasm/mod/ffi/types.c b/ports/wapy-wasm/mod/ffi/types.c new file mode 100644 index 000000000..c86c73574 --- /dev/null +++ b/ports/wapy-wasm/mod/ffi/types.c @@ -0,0 +1,108 @@ +/* ----------------------------------------------------------------------- + types.c - Copyright (c) 1996, 1998 Red Hat, Inc. + + Predefined ffi_types needed by libffi. + + 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. + ----------------------------------------------------------------------- */ + +/* Hide the basic type definitions from the header file, so that we + can redefine them here as "const". */ +#define LIBFFI_HIDE_BASIC_TYPES + +#include "ffi.h" +#include "ffi_common.h" + +/* Type definitions */ + +#define FFI_TYPEDEF(name, type, id, maybe_const)\ +struct struct_align_##name { \ + char c; \ + type x; \ +}; \ +FFI_EXTERN \ +maybe_const ffi_type ffi_type_##name = { \ + sizeof(type), \ + offsetof(struct struct_align_##name, x), \ + id, NULL \ +} + +#define FFI_COMPLEX_TYPEDEF(name, type, maybe_const) \ +static ffi_type *ffi_elements_complex_##name [2] = { \ + (ffi_type *)(&ffi_type_##name), NULL \ +}; \ +struct struct_align_complex_##name { \ + char c; \ + _Complex type x; \ +}; \ +FFI_EXTERN \ +maybe_const ffi_type ffi_type_complex_##name = { \ + sizeof(_Complex type), \ + offsetof(struct struct_align_complex_##name, x), \ + FFI_TYPE_COMPLEX, \ + (ffi_type **)ffi_elements_complex_##name \ +} + +/* Size and alignment are fake here. They must not be 0. */ +FFI_EXTERN const ffi_type ffi_type_void = { + 1, 1, FFI_TYPE_VOID, NULL +}; + +FFI_TYPEDEF(uint8, UINT8, FFI_TYPE_UINT8, const); +FFI_TYPEDEF(sint8, SINT8, FFI_TYPE_SINT8, const); +FFI_TYPEDEF(uint16, UINT16, FFI_TYPE_UINT16, const); +FFI_TYPEDEF(sint16, SINT16, FFI_TYPE_SINT16, const); +FFI_TYPEDEF(uint32, UINT32, FFI_TYPE_UINT32, const); +FFI_TYPEDEF(sint32, SINT32, FFI_TYPE_SINT32, const); +FFI_TYPEDEF(uint64, UINT64, FFI_TYPE_UINT64, const); +FFI_TYPEDEF(sint64, SINT64, FFI_TYPE_SINT64, const); + +FFI_TYPEDEF(pointer, void*, FFI_TYPE_POINTER, const); + +FFI_TYPEDEF(float, float, FFI_TYPE_FLOAT, const); +FFI_TYPEDEF(double, double, FFI_TYPE_DOUBLE, const); + +#if !defined HAVE_LONG_DOUBLE_VARIANT || defined __alpha__ +#define FFI_LDBL_CONST const +#else +#define FFI_LDBL_CONST +#endif + +#ifdef __alpha__ +/* Even if we're not configured to default to 128-bit long double, + maintain binary compatibility, as -mlong-double-128 can be used + at any time. */ +/* Validate the hard-coded number below. */ +# if defined(__LONG_DOUBLE_128__) && FFI_TYPE_LONGDOUBLE != 4 +# error FFI_TYPE_LONGDOUBLE out of date +# endif +const ffi_type ffi_type_longdouble = { 16, 16, 4, NULL }; +#elif FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE +FFI_TYPEDEF(longdouble, long double, FFI_TYPE_LONGDOUBLE, FFI_LDBL_CONST); +#endif + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_COMPLEX_TYPEDEF(float, float, const); +FFI_COMPLEX_TYPEDEF(double, double, const); +#if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE +FFI_COMPLEX_TYPEDEF(longdouble, long double, FFI_LDBL_CONST); +#endif +#endif diff --git a/ports/wapy-wasm/mod/modffi.c b/ports/wapy-wasm/mod/modffi.c new file mode 100644 index 000000000..4a5127890 --- /dev/null +++ b/ports/wapy-wasm/mod/modffi.c @@ -0,0 +1,515 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include + +#if defined(__EMSCRIPTEN__) // N/I || defined(__WASI__) + #include +#else + #include "ffi.h" +#endif + +#include + +#include "py/runtime.h" +#include "py/binary.h" +#include "py/mperrno.h" + +/* + * modffi uses character codes to encode a value type, based on "struct" + * module type codes, with some extensions and overridings. + * + * Extra/overridden typecodes: + * v - void, can be used only as return type + * P - const void*, pointer to read-only memory + * p - void*, meaning pointer to a writable memory (note that this + * clashes with struct's "p" as "Pascal string"). + * s - as argument, the same as "p", as return value, causes string + * to be allocated and returned, instead of pointer value. + * O - mp_obj_t, passed as is (mostly useful as a callback param) + * + * TODO: + * C - callback function + * + * Note: all constraint specified by typecode can be not enforced at this time, + * but may be later. + */ + +typedef struct _mp_obj_opaque_t { + mp_obj_base_t base; + void *val; +} mp_obj_opaque_t; + +typedef struct _mp_obj_ffimod_t { + mp_obj_base_t base; + void *handle; +} mp_obj_ffimod_t; + +typedef struct _mp_obj_ffivar_t { + mp_obj_base_t base; + void *var; + char type; +// ffi_type *type; +} mp_obj_ffivar_t; + +typedef struct _mp_obj_ffifunc_t { + mp_obj_base_t base; + void *func; + char rettype; + const char *argtypes; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_ffifunc_t; + +typedef struct _mp_obj_fficallback_t { + mp_obj_base_t base; + void *func; + ffi_closure *clo; + char rettype; + ffi_cif cif; + ffi_type *params[]; +} mp_obj_fficallback_t; + +//STATIC const mp_obj_type_t opaque_type; +STATIC const mp_obj_type_t ffimod_type; +STATIC const mp_obj_type_t ffifunc_type; +STATIC const mp_obj_type_t fficallback_type; +STATIC const mp_obj_type_t ffivar_type; + +STATIC ffi_type *char2ffi_type(char c) +{ + switch (c) { + case 'b': return &ffi_type_schar; + case 'B': return &ffi_type_uchar; + case 'h': return &ffi_type_sshort; + case 'H': return &ffi_type_ushort; + case 'i': return &ffi_type_sint; + case 'I': return &ffi_type_uint; + case 'l': return &ffi_type_slong; + case 'L': return &ffi_type_ulong; + case 'q': return &ffi_type_sint64; + case 'Q': return &ffi_type_uint64; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': return &ffi_type_float; + case 'd': return &ffi_type_double; + #endif + case 'O': // mp_obj_t + case 'C': // (*)() + case 'P': // const void* + case 'p': // void* + case 's': return &ffi_type_pointer; + case 'v': return &ffi_type_void; + default: return NULL; + } +} + +STATIC ffi_type *get_ffi_type(mp_obj_t o_in) +{ + if (MP_OBJ_IS_STR(o_in)) { + const char *s = mp_obj_str_get_str(o_in); + ffi_type *t = char2ffi_type(*s); + if (t != NULL) { + return t; + } + } + // TODO: Support actual libffi type objects + + mp_raise_TypeError("Unknown type"); +} + +STATIC mp_obj_t return_ffi_value(ffi_arg val, char type) +{ + switch (type) { + case 's': { + const char *s = (const char *)(intptr_t)val; + if (!s) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); + } + case 'v': + return mp_const_none; + #if MICROPY_PY_BUILTINS_FLOAT + case 'f': { + union { ffi_arg ffi; float flt; } val_union = { .ffi = val }; + return mp_obj_new_float(val_union.flt); + } + case 'd': { + double *p = (double*)&val; + return mp_obj_new_float(*p); + } + #endif + case 'O': + return (mp_obj_t)(intptr_t)val; + default: + return mp_obj_new_int(val); + } +} + +// FFI module + +STATIC void ffimod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->handle); +} + +STATIC mp_obj_t ffimod_close(mp_obj_t self_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + dlclose(self->handle); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(ffimod_close_obj, ffimod_close); + +STATIC mp_obj_t make_func(mp_obj_t rettype_in, void *func, mp_obj_t argtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + const char *argtypes = mp_obj_str_get_str(argtypes_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(argtypes_in)); + mp_obj_ffifunc_t *o = m_new_obj_var(mp_obj_ffifunc_t, ffi_type*, nparams); + o->base.type = &ffifunc_type; + + o->func = func; + o->rettype = *rettype; + o->argtypes = argtypes; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(argtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + mp_raise_ValueError("Error in ffi_prep_cif"); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t ffimod_func(size_t n_args, const mp_obj_t *args) { + (void)n_args; // always 4 + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(args[0]); + const char *symname = mp_obj_str_get_str(args[2]); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + mp_raise_OSError(MP_ENOENT); + } + return make_func(args[1], sym, args[3]); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ffimod_func_obj, 4, 4, ffimod_func); + +STATIC mp_obj_t mod_ffi_func(mp_obj_t rettype, mp_obj_t addr_in, mp_obj_t argtypes) { + void *addr = (void*)MP_OBJ_TO_PTR(mp_obj_int_get_truncated(addr_in)); + return make_func(rettype, addr, argtypes); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_func_obj, mod_ffi_func); + + +STATIC void call_py_func(ffi_cif *cif, void *ret, void** args, void *func) { + mp_obj_t pyargs[cif->nargs]; + for (uint i = 0; i < cif->nargs; i++) { + pyargs[i] = mp_obj_new_int(*(mp_int_t*)args[i]); + } + mp_obj_t res = mp_call_function_n_kw(MP_OBJ_FROM_PTR(func), cif->nargs, 0, pyargs); + + if (res != mp_const_none) { + *(ffi_arg*)ret = mp_obj_int_get_truncated(res); + } +} + + +STATIC mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t paramtypes_in) { + const char *rettype = mp_obj_str_get_str(rettype_in); + + mp_int_t nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(paramtypes_in)); + mp_obj_fficallback_t *o = m_new_obj_var(mp_obj_fficallback_t, ffi_type*, nparams); + o->base.type = &fficallback_type; + + o->clo = ffi_closure_alloc(sizeof(ffi_closure), &o->func); + + o->rettype = *rettype; + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(paramtypes_in, &iter_buf); + mp_obj_t item; + int i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + o->params[i++] = get_ffi_type(item); + } + + int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params); + if (res != FFI_OK) { + mp_raise_ValueError("Error in ffi_prep_cif"); + } +/* #FIXME: PMPP */ + res = ffi_prep_closure_loc(o->clo, &o->cif, call_py_func, MP_OBJ_TO_PTR(func_in), o->func); + if (res != FFI_OK) { + mp_raise_ValueError("ffi_prep_closure_loc"); + } + + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(mod_ffi_callback_obj, mod_ffi_callback); + +STATIC mp_obj_t ffimod_var(mp_obj_t self_in, mp_obj_t vartype_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *rettype = mp_obj_str_get_str(vartype_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + mp_raise_OSError(MP_ENOENT); + return MP_OBJ_NULL; + } + mp_obj_ffivar_t *o = m_new_obj(mp_obj_ffivar_t); + o->base.type = &ffivar_type; + + o->var = sym; + o->type = *rettype; + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(ffimod_var_obj, ffimod_var); + +STATIC mp_obj_t ffimod_addr(mp_obj_t self_in, mp_obj_t symname_in) { + mp_obj_ffimod_t *self = MP_OBJ_TO_PTR(self_in); + const char *symname = mp_obj_str_get_str(symname_in); + + void *sym = dlsym(self->handle, symname); + if (sym == NULL) { + mp_raise_OSError(MP_ENOENT); + } + return mp_obj_new_int((uintptr_t)sym); +} +MP_DEFINE_CONST_FUN_OBJ_2(ffimod_addr_obj, ffimod_addr); + +STATIC mp_obj_t ffimod_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)n_args; + (void)n_kw; + + const char *fname = NULL; + if (args[0] != mp_const_none) { + fname = mp_obj_str_get_str(args[0]); + } + void *mod = dlopen(fname, RTLD_NOW | RTLD_LOCAL); + + if (mod == NULL) { + mp_raise_OSError(errno); + } + mp_obj_ffimod_t *o = m_new_obj(mp_obj_ffimod_t); + o->base.type = type; + o->handle = mod; + return MP_OBJ_FROM_PTR(o); +} + +STATIC const mp_rom_map_elem_t ffimod_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&ffimod_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_var), MP_ROM_PTR(&ffimod_var_obj) }, + { MP_ROM_QSTR(MP_QSTR_addr), MP_ROM_PTR(&ffimod_addr_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&ffimod_close_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffimod_locals_dict, ffimod_locals_dict_table); + +STATIC const mp_obj_type_t ffimod_type = { + { &mp_type_type }, + .name = MP_QSTR_ffimod, + .print = ffimod_print, + .make_new = ffimod_make_new, + .locals_dict = (mp_obj_dict_t*)&ffimod_locals_dict, +}; + +// FFI function + +STATIC void ffifunc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC mp_obj_t ffifunc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_ffifunc_t *self = MP_OBJ_TO_PTR(self_in); + assert(n_kw == 0); + assert(n_args == self->cif.nargs); + + ffi_arg values[n_args]; + void *valueptrs[n_args]; + const char *argtype = self->argtypes; + for (uint i = 0; i < n_args; i++, argtype++) { + mp_obj_t a = args[i]; + if (*argtype == 'O') { + values[i] = (ffi_arg)(intptr_t)a; + #if MICROPY_PY_BUILTINS_FLOAT + } else if (*argtype == 'f') { + float *p = (float*)&values[i]; + *p = mp_obj_get_float(a); + } else if (*argtype == 'd') { + double *p = (double*)&values[i]; + *p = mp_obj_get_float(a); + #endif + } else if (a == mp_const_none) { + values[i] = 0; + } else if (MP_OBJ_IS_INT(a)) { + values[i] = mp_obj_int_get_truncated(a); + } else if (MP_OBJ_IS_STR(a)) { + const char *s = mp_obj_str_get_str(a); + values[i] = (ffi_arg)(intptr_t)s; + } else if (((mp_obj_base_t*)MP_OBJ_TO_PTR(a))->type->buffer_p.get_buffer != NULL) { + mp_obj_base_t *o = (mp_obj_base_t*)MP_OBJ_TO_PTR(a); + mp_buffer_info_t bufinfo; + int ret = o->type->buffer_p.get_buffer(MP_OBJ_FROM_PTR(o), &bufinfo, MP_BUFFER_READ); // TODO: MP_BUFFER_READ? + if (ret != 0) { + goto error; + } + values[i] = (ffi_arg)(intptr_t)bufinfo.buf; + } else if (MP_OBJ_IS_TYPE(a, &fficallback_type)) { + mp_obj_fficallback_t *p = MP_OBJ_TO_PTR(a); + values[i] = (ffi_arg)(intptr_t)p->func; + } else { + goto error; + } + valueptrs[i] = &values[i]; + } + + // If ffi_arg is not big enough to hold a double, then we must pass along a + // pointer to a memory location of the correct size. + // TODO check if this needs to be done for other types which don't fit into + // ffi_arg. + #if MICROPY_PY_BUILTINS_FLOAT + if (sizeof(ffi_arg) == 4 && self->rettype == 'd') { + double retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return mp_obj_new_float(retval); + } else + #endif + { + ffi_arg retval; + ffi_call(&self->cif, self->func, &retval, valueptrs); + return return_ffi_value(retval, self->rettype); + } + +error: + mp_raise_TypeError("Don't know how to pass object to native function"); +} + +STATIC const mp_obj_type_t ffifunc_type = { + { &mp_type_type }, + .name = MP_QSTR_ffifunc, + .print = ffifunc_print, + .call = ffifunc_call, +}; + +// FFI callback for Python function + +STATIC void fficallback_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fficallback_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->func); +} + +STATIC const mp_obj_type_t fficallback_type = { + { &mp_type_type }, + .name = MP_QSTR_fficallback, + .print = fficallback_print, +}; + +// FFI variable + +STATIC void ffivar_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + // Variable value printed as cast to int + mp_printf(print, "", self->var, *(int*)self->var); +} + +STATIC mp_obj_t ffivar_get(mp_obj_t self_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + return mp_binary_get_val_array(self->type, self->var, 0); +} +MP_DEFINE_CONST_FUN_OBJ_1(ffivar_get_obj, ffivar_get); + +STATIC mp_obj_t ffivar_set(mp_obj_t self_in, mp_obj_t val_in) { + mp_obj_ffivar_t *self = MP_OBJ_TO_PTR(self_in); + mp_binary_set_val_array(self->type, self->var, 0, val_in); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(ffivar_set_obj, ffivar_set); + +STATIC const mp_rom_map_elem_t ffivar_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&ffivar_get_obj) }, + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&ffivar_set_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(ffivar_locals_dict, ffivar_locals_dict_table); + +STATIC const mp_obj_type_t ffivar_type = { + { &mp_type_type }, + .name = MP_QSTR_ffivar, + .print = ffivar_print, + .locals_dict = (mp_obj_dict_t*)&ffivar_locals_dict, +}; + +// Generic opaque storage object (unused) + +/* +STATIC const mp_obj_type_t opaque_type = { + { &mp_type_type }, + .name = MP_QSTR_opaqueval, +// .print = opaque_print, +}; +*/ + +STATIC mp_obj_t mod_ffi_open(size_t n_args, const mp_obj_t *args) { + return ffimod_make_new(&ffimod_type, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_ffi_open_obj, 1, 2, mod_ffi_open); + +STATIC mp_obj_t mod_ffi_as_bytearray(mp_obj_t ptr, mp_obj_t size) { + return mp_obj_new_bytearray_by_ref(mp_obj_int_get_truncated(size), (void*)(uintptr_t)mp_obj_int_get_truncated(ptr)); +} +MP_DEFINE_CONST_FUN_OBJ_2(mod_ffi_as_bytearray_obj, mod_ffi_as_bytearray); + +STATIC const mp_rom_map_elem_t mp_module_ffi_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ffi) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mod_ffi_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_callback), MP_ROM_PTR(&mod_ffi_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_func), MP_ROM_PTR(&mod_ffi_func_obj) }, + { MP_ROM_QSTR(MP_QSTR_as_bytearray), MP_ROM_PTR(&mod_ffi_as_bytearray_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ffi_globals, mp_module_ffi_globals_table); + +const mp_obj_module_t mp_module_ffi = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ffi_globals, +}; diff --git a/ports/wapy-wasm/mod/modos.c b/ports/wapy-wasm/mod/modos.c new file mode 100644 index 000000000..06a43d728 --- /dev/null +++ b/ports/wapy-wasm/mod/modos.c @@ -0,0 +1,254 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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 +#include +#include "py/mpconfig.h" + +#include "py/runtime.h" +#include "py/objtuple.h" +#include "py/mphal.h" +#include "extmod/vfs.h" +#include "extmod/misc.h" + +#ifdef __ANDROID__ +#define USE_STATFS 1 +#endif + +STATIC mp_obj_t mod_os_stat(mp_obj_t path_in) { + struct stat sb; + const char *path = mp_obj_str_get_str(path_in); + + int res = stat(path, &sb); + RAISE_ERRNO(res, errno); + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.st_mode); + t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.st_ino); + t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.st_dev); + t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.st_nlink); + t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.st_uid); + t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.st_gid); + t->items[6] = mp_obj_new_int_from_uint(sb.st_size); + t->items[7] = MP_OBJ_NEW_SMALL_INT(sb.st_atime); + t->items[8] = MP_OBJ_NEW_SMALL_INT(sb.st_mtime); + t->items[9] = MP_OBJ_NEW_SMALL_INT(sb.st_ctime); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_stat_obj, mod_os_stat); + +#if MICROPY_PY_OS_STATVFS + +#if USE_STATFS +#include +#define STRUCT_STATVFS struct statfs +#define STATVFS statfs +#define F_FAVAIL sb.f_ffree +#define F_NAMEMAX sb.f_namelen +#define F_FLAG sb.f_flags +#else +#include +#define STRUCT_STATVFS struct statvfs +#define STATVFS statvfs +#define F_FAVAIL sb.f_favail +#define F_NAMEMAX sb.f_namemax +#define F_FLAG sb.f_flag +#endif + +STATIC mp_obj_t mod_os_statvfs(mp_obj_t path_in) { + STRUCT_STATVFS sb; + const char *path = mp_obj_str_get_str(path_in); + + int res = STATVFS(path, &sb); + RAISE_ERRNO(res, errno); + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.f_bsize); + t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.f_frsize); + t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.f_blocks); + t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.f_bfree); + t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.f_bavail); + t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.f_files); + t->items[6] = MP_OBJ_NEW_SMALL_INT(sb.f_ffree); + t->items[7] = MP_OBJ_NEW_SMALL_INT(F_FAVAIL); + t->items[8] = MP_OBJ_NEW_SMALL_INT(F_FLAG); + t->items[9] = MP_OBJ_NEW_SMALL_INT(F_NAMEMAX); + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_statvfs_obj, mod_os_statvfs); +#endif + +STATIC mp_obj_t mod_os_remove(mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + // Note that POSIX requires remove() to be able to delete a directory + // too (act as rmdir()). This is POSIX extenstion to ANSI C semantics + // of that function. But Python remove() follows ANSI C, and explicitly + // required to raise exception on attempt to remove a directory. Thus, + // call POSIX unlink() here. + int r = unlink(path); + + RAISE_ERRNO(r, errno); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_remove_obj, mod_os_remove); + +STATIC mp_obj_t mod_os_system(mp_obj_t cmd_in) { + const char *cmd = mp_obj_str_get_str(cmd_in); +#if defined(__EMSCRIPTEN__) || defined(__WASI__) + int r = 1; +#else + int r = system(cmd); +#endif + RAISE_ERRNO(r, errno); + + return MP_OBJ_NEW_SMALL_INT(r); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_system_obj, mod_os_system); + +STATIC mp_obj_t mod_os_getenv(mp_obj_t var_in) { + const char *s = getenv(mp_obj_str_get_str(var_in)); + if (s == NULL) { + return mp_const_none; + } + return mp_obj_new_str(s, strlen(s)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mod_os_getenv_obj, mod_os_getenv); + +STATIC mp_obj_t mod_os_mkdir(mp_obj_t path_in) { + // TODO: Accept mode param + const char *path = mp_obj_str_get_str(path_in); + #ifdef _WIN32 + int r = mkdir(path); + #else + int r = mkdir(path, 0777); + #endif + RAISE_ERRNO(r, errno); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_os_mkdir_obj, mod_os_mkdir); + +typedef struct _mp_obj_listdir_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + DIR *dir; +} mp_obj_listdir_t; + +STATIC mp_obj_t listdir_next(mp_obj_t self_in) { + mp_obj_listdir_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->dir == NULL) { + goto done; + } + struct dirent *dirent = readdir(self->dir); + if (dirent == NULL) { + closedir(self->dir); + self->dir = NULL; + done: + return MP_OBJ_STOP_ITERATION; + } + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); + t->items[0] = mp_obj_new_str(dirent->d_name, strlen(dirent->d_name)); + + #ifdef _DIRENT_HAVE_D_TYPE + #ifdef DTTOIF + t->items[1] = MP_OBJ_NEW_SMALL_INT(DTTOIF(dirent->d_type)); + #else + if (dirent->d_type == DT_DIR) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); + } else if (dirent->d_type == DT_REG) { + t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG); + } else { + t->items[1] = MP_OBJ_NEW_SMALL_INT(dirent->d_type); + } + #endif + #else + // DT_UNKNOWN should have 0 value on any reasonable system + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); + #endif + + #ifdef _DIRENT_HAVE_D_INO + t->items[2] = MP_OBJ_NEW_SMALL_INT(dirent->d_ino); + #else + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); + #endif + return MP_OBJ_FROM_PTR(t); +} + +STATIC mp_obj_t mod_os_ilistdir(size_t n_args, const mp_obj_t *args) { + const char *path = "."; + if (n_args > 0) { + path = mp_obj_str_get_str(args[0]); + } + mp_obj_listdir_t *o = m_new_obj(mp_obj_listdir_t); + o->base.type = &mp_type_polymorph_iter; + o->dir = opendir(path); + o->iternext = listdir_next; + return MP_OBJ_FROM_PTR(o); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_ilistdir_obj, 0, 1, mod_os_ilistdir); + +STATIC mp_obj_t mod_os_errno(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return MP_OBJ_NEW_SMALL_INT(errno); + } + + errno = mp_obj_get_int(args[0]); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj, 0, 1, mod_os_errno); + +STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, + { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mod_os_stat_obj) }, + #if MICROPY_PY_OS_STATVFS + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mod_os_statvfs_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_system), MP_ROM_PTR(&mod_os_system_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mod_os_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mod_os_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mod_os_ilistdir_obj) }, + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_os_globals, mp_module_os_globals_table); + +const mp_obj_module_t mp_module_os = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_os_globals, +}; diff --git a/ports/wapy-wasm/mod/modtime.c b/ports/wapy-wasm/mod/modtime.c new file mode 100644 index 000000000..9f5bd0efc --- /dev/null +++ b/ports/wapy-wasm/mod/modtime.c @@ -0,0 +1,195 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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/mpconfig.h" + + +#include +#include +#include +#include +#include + +#if defined(__EMSCRIPTEN__) + #include + #define wa_clock_gettime(clockid, timespec) clock_gettime(clockid, timespec) + #define wa_gettimeofday(timeval, tmz) gettimeofday(timeval, tmz) +#endif + + +extern struct timespec t_timespec; +extern struct timeval t_timeval; + + + +#include + +#include "py/runtime.h" +#include "py/smallint.h" +#include "py/mphal.h" +#include "extmod/utime_mphal.h" + +#include + +// mingw32 defines CLOCKS_PER_SEC as ((clock_t)) but preprocessor does not handle casts +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) +#define MP_REMOVE_BRACKETSA(x) +#define MP_REMOVE_BRACKETSB(x) MP_REMOVE_BRACKETSA x +#define MP_REMOVE_BRACKETSC(x) MP_REMOVE_BRACKETSB x +#define MP_CLOCKS_PER_SEC MP_REMOVE_BRACKETSC(CLOCKS_PER_SEC) +#else +#define MP_CLOCKS_PER_SEC CLOCKS_PER_SEC +#endif + +#if defined(MP_CLOCKS_PER_SEC) +#define CLOCK_DIV (MP_CLOCKS_PER_SEC / 1000.0F) +#else +#error Unsupported clock() implementation +#endif + + +// Note: this is deprecated since CPy3.3, but pystone still uses it. +STATIC mp_obj_t +mod_time_clock(void) { +#if MICROPY_PY_BUILTINS_FLOAT + // float cannot represent full range of int32 precisely, so we pre-divide + // int to reduce resolution, and then actually do float division hoping + // to preserve integer part resolution. + return mp_obj_new_float((float)(clock() / 1000) / CLOCK_DIV); +#else + return mp_obj_new_int((mp_int_t)clock()); +#endif +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_clock_obj, mod_time_clock); + + +STATIC mp_obj_t mod_time_time(void) { + + wa_gettimeofday(&t_timeval, NULL); + +#if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = t_timeval.tv_sec + (mp_float_t)t_timeval.tv_usec / 1000000; + return mp_obj_new_float(val); +#else + return mp_obj_new_int_from_uint( t_timeval.tv_sec ); +#endif +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_time_obj, mod_time_time); + +STATIC mp_obj_t mod_time_time_ns(void) { + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + return mp_obj_new_int_from_ull( t_timespec.tv_sec * 1000000000ULL + t_timespec.tv_nsec ); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_time_time_ns_obj, mod_time_time_ns); + + + +STATIC +mp_obj_t mod_time_sleep(mp_obj_t arg) { + //NO SYNC SLEEP on wasm, use asyncio + printf("\nmod_time_sleep\n"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_obj, mod_time_sleep); + + +STATIC +mp_obj_t mod_time_sleep_ms(mp_obj_t arg) { + //NO SYNC SLEEP on wasm, use asyncio + printf("\nmod_time_sleep_ms\n"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_ms_obj, mod_time_sleep_ms); + +STATIC +mp_obj_t mod_time_sleep_us(mp_obj_t arg) { + //NO SYNC SLEEP on wasm, use asyncio + printf("\nmod_time_sleep_us\n"); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_us_obj, mod_time_sleep_us); + + + +STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { + time_t t; + if (n_args == 0) { + t = time(NULL); + } else { + #if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(args[0]); + t = (time_t)MICROPY_FLOAT_C_FUN(trunc)(val); + #else + t = mp_obj_get_int(args[0]); + #endif + } + struct tm *tm = localtime(&t); + + mp_obj_t ret = mp_obj_new_tuple(9, NULL); + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(ret); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(tm->tm_year + 1900); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(tm->tm_mon + 1); + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(tm->tm_mday); + tuple->items[3] = MP_OBJ_NEW_SMALL_INT(tm->tm_hour); + tuple->items[4] = MP_OBJ_NEW_SMALL_INT(tm->tm_min); + tuple->items[5] = MP_OBJ_NEW_SMALL_INT(tm->tm_sec); + int wday = tm->tm_wday - 1; + if (wday < 0) { + wday = 6; + } + tuple->items[6] = MP_OBJ_NEW_SMALL_INT(wday); + tuple->items[7] = MP_OBJ_NEW_SMALL_INT(tm->tm_yday + 1); + tuple->items[8] = MP_OBJ_NEW_SMALL_INT(tm->tm_isdst); + + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_time_localtime_obj, 0, 1, mod_time_localtime); + +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_clock), MP_ROM_PTR(&mod_time_clock_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mod_time_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mod_time_sleep_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mod_time_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&mod_time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mod_time_time_ns_obj) }, + { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&mod_time_localtime_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_time = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_time_globals, +}; + diff --git a/ports/wapy-wasm/mod/modules.h b/ports/wapy-wasm/mod/modules.h new file mode 100644 index 000000000..8b7362eba --- /dev/null +++ b/ports/wapy-wasm/mod/modules.h @@ -0,0 +1,13 @@ +#ifndef INCLUDE_MODULES_H +#define INCLUDE_MODULES_H 1 + +//extern const struct _mp_obj_module_t mp_module_embed; + + +#define MODULES_LINKS +// { MP_OBJ_NEW_QSTR(MP_QSTR_embed), MP_ROM_PTR(&mp_module_embed) }, + + +#endif + +//EOF diff --git a/ports/wapy-wasm/mod/moduos_vfs.c b/ports/wapy-wasm/mod/moduos_vfs.c new file mode 100644 index 000000000..e9ac8e1f8 --- /dev/null +++ b/ports/wapy-wasm/mod/moduos_vfs.c @@ -0,0 +1,83 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 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 "extmod/vfs.h" +#include "extmod/vfs_posix.h" +#include "extmod/vfs_fat.h" + +#if MICROPY_VFS + +// These are defined in modos.c +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mod_os_errno_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_getenv_obj); +MP_DECLARE_CONST_FUN_OBJ_1(mod_os_system_obj); + +STATIC const mp_rom_map_elem_t uos_vfs_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos_vfs) }, + { MP_ROM_QSTR(MP_QSTR_sep), MP_ROM_QSTR(MP_QSTR__slash_) }, + + { MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mod_os_errno_obj) }, + { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) }, + { MP_ROM_QSTR(MP_QSTR_system), MP_ROM_PTR(&mod_os_system_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) }, + + { 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_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_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) }, + { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove + + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif + + #if MICROPY_VFS_POSIX + { MP_ROM_QSTR(MP_QSTR_VfsPosix), MP_ROM_PTR(&mp_type_vfs_posix) }, + #endif + #if MICROPY_VFS_FAT + { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(uos_vfs_module_globals, uos_vfs_module_globals_table); + +const mp_obj_module_t mp_module_uos_vfs = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&uos_vfs_module_globals, +}; + +#endif // MICROPY_VFS diff --git a/ports/wapy-wasm/mpconfigport.h b/ports/wapy-wasm/mpconfigport.h new file mode 100644 index 000000000..26dfe93a7 --- /dev/null +++ b/ports/wapy-wasm/mpconfigport.h @@ -0,0 +1,506 @@ +#define MODULES_LINKS + +#include "dev_conf.h" + +#if __DEV__ +#define MICROPY_VM_HOOK_BC 1 + +#ifdef STATIC + #pragam message "STATIC as static could lead to linking problems" +#endif + +//#define STATIC // mpconfig.h:1402 => bad pointer cast and bad linking +#endif + + +#define MICROPY_ROM_TEXT_COMPRESSION (0) +#define MICROPY_PY_FSTRING (1) + +#ifndef NO_NLR + #define NO_NLR (1) + #define WAPY (1) + #define __WASI__ 1 +#endif + +#define MICROPY_EPOCH_IS_1970 (0) + +#define False false +#define True true + + +#define mp_hal_ticks_cpu() 0 + +#ifdef MICROPY_OBJ_IMMEDIATE_OBJS +//https://github.com/pfalcon/pycopy/commit/2a362af5bcdeaadd94ddac7f90ea33092a7ac7be +#undef MICROPY_OBJ_IMMEDIATE_OBJS +#define MICROPY_OBJ_IMMEDIATE_OBJS (0) +#endif + +// options to control how Micro Python is built +#define MICROPY_QSTR_BYTES_IN_HASH (1) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_CPYTHON_COMPAT (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +//just in case of long urls and a local cache ? +#define MICROPY_ALLOC_PATH_MAX (1024) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) + +#if 0 + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (1) +#else + //#pragma message "build/frozen_mpy.c:7:2: error: 'incompatible MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE'" + #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) +#endif + +#define MICROPY_MODULE_WEAK_LINKS (0) + +#define MICROPY_MODULE_GETATTR (1) +#define MICROPY_MODULE_SPECIAL_METHODS (1) + + +#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool + +#define MICROPY_PY_BUILTINS_FROZENSET (1) + +#define MICROPY_LONGINT_IMPL_MPZ (2) +#if 1 + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#else + #pragma message "build/frozen_mpy.c:12:2: error: 'incompatible MICROPY_LONGINT_IMPL'" + #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG) +#endif + +#define MPZ_DIG_SIZE (16) + +#define MICROPY_ENABLE_COMPILER (1) +#define MICROPY_ENABLE_EXTERNAL_IMPORT (1) +#define MICROPY_PY_BUILTINS_COMPILE (1) +#define MICROPY_PY_SYS_EXC_INFO (0) // <============================ NON STANDARD PY2 + +#define MICROPY_PERSISTENT_CODE (1) +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_PERSISTENT_CODE_SAVE (1) +//#define MICROPY_EMIT_MACHINE_CODE (1) + + +#define MICROPY_HAS_FILE_READER (1) +#define MICROPY_HELPER_LEXER_UNIX (1) + +#define MICROPY_READER_POSIX (0) +#define MICROPY_VFS_POSIX (0) + +#define HAVE_mp_raw_code_save_file (1) + +#define MICROPY_EMIT_WASM (1) + +//MICROPY_EMIT_INLINE_ASM ((MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA) +//#define MICROPY_EMIT_NATIVE + +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_INLINE_THUMB (0) + + +#if 1 +// 0 0 0 0 ! RuntimeError: memory access out of bounds +// 0 0 0 1 ! silent crash +// 0 0 1 0 = 11.15 Kpy/s + #define MICROPY_STACKLESS (0) + #define MICROPY_STACKLESS_STRICT (0) + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#else +// 1 0 1 1 = 10.6 Kpy/s +// 1 0 1 0 = 10.8 K +// 1 1 1 0 = 10.6 +// 1 1 1 1 = 10.6 + #define MICROPY_STACKLESS (1) + #define MICROPY_STACKLESS_STRICT (1) // <=============== 1! or too much collect + #define MICROPY_ENABLE_PYSTACK (1) + #define MICROPY_OPT_COMPUTED_GOTO (0) +#endif + +// nlr.h MICROPY_NLR_* must match a supported arch +// define or autodetect will fail to select WASM + +#if defined(__EMSCRIPTEN__) || defined(__WASI__) + #define MICROPY_NLR_SETJMP (0) +#else + #define MICROPY_NLR_SETJMP (1) +#endif + +#define MICROPY_DYNAMIC_COMPILER (0) + +#if MICROPY_NLR_SETJMP + // can't have MICROPY_NLR_SETJMP==MICROPY_DYNAMIC_COMPILER==0 + #define MICROPY_NLR_OS_WINDOWS (0) + #define MICROPY_NLR_X86 (0) + #define MICROPY_NLR_X64 (0) +#endif + +// mpconfig +#define MICROPY_PY_GENERATOR_PEND_THROW (1) // def 1 +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) // def 0 + + +// MEMORY +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_GCREGS_SETJMP (1) +#define MICROPY_GC_ALLOC_THRESHOLD (1) + + +//like ESP/UNIX +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (64) + + +#if MICROPY_PY_LVGL +#define MICROPY_MEM_STATS (0) +#else +#define MICROPY_MEM_STATS (1) +#endif + +#if MICROPY_MEM_STATS +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (1) +#endif + + +#define MICROPY_KBD_EXCEPTION (1) + + +//?? +#define MICROPY_USE_INTERNAL_ERRNO (0) + +#define MICROPY_USE_READLINE (0) +#define MICROPY_PY_BUILTINS_INPUT (1) + + +#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#define MICROPY_PY_DELATTR_SETATTR (1) +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +#define MICROPY_PY_MATH_FACTORIAL (1) + + +#define MICROPY_COMP_MODULE_CONST (1) + +#if 0 + #define MICROPY_COMP_CONST (0) + #define MICROPY_PY_SYS_SETTRACE (1) +#else + #define MICROPY_COMP_CONST (1) + #define MICROPY_PY_SYS_SETTRACE (0) +#endif +#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (1) + +#define MICROPY_ENABLE_SOURCE_LINE (1) + +#define MICROPY_HELPER_REPL (1) + +#define MICROPY_PY___FILE__ (1) + +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) + +#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ASYNC_AWAIT (1) +#define MICROPY_PY_ATTRTUPLE (1) + +#define MICROPY_PY_BTREE (0) + +#define MICROPY_PY_BUILTINS_COMPLEX (1) // required +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_BUILTINS_ENUMERATE (1) +#define MICROPY_PY_BUILTINS_EXECFILE (1) // <============================ NON STANDARD PY2 +#define MICROPY_PY_BUILTINS_FILTER (1) +//#define MICROPY_PY_BUILTINS_FLOAT (1) because MICROPY_FLOAT_IMPL_DOUBLE +#define MICROPY_PY_BUILTINS_HELP_MODULES (0) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_MIN_MAX (1) +#define MICROPY_PY_BUILTINS_NEXT2 (1) // <=============== next2 make next() compat with cpython +#define MICROPY_PY_BUILTINS_PROPERTY (1) +#define MICROPY_PY_BUILTINS_REVERSED (1) +#define MICROPY_PY_BUILTINS_SET (1) +#define MICROPY_PY_BUILTINS_SLICE (1) + +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#if MICROPY_PY_BUILTINS_STR_UNICODE + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (1) +#else + #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (0) + #if NO_NLR + //#error "unsupported native storage must be utf8" + #endif +#endif + +#define MICROPY_PY_BUILTINS_STR_CENTER (1) +#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) +#define MICROPY_PY_BUILTINS_SLICE_ATTRS (1) + +#define MICROPY_PY_COLLECTIONS (1) +#define MICROPY_PY_CMATH (1) + +//? TEST THAT THING ! +#if defined(__EMSCRIPTEN__) + #ifndef MICROPY_PY_FFI + #define MICROPY_PY_FFI (1) // <========================== NON STANDARD NOT CPY + #endif +#else + #define MICROPY_PY_FFI (0) +#endif + + +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_PY_GC (1) +#define MICROPY_PY_MATH (1) +#define MICROPY_PY_STRUCT (1) +#define MICROPY_PY_SYS (1) +#define MICROPY_PY_SYS_GETSIZEOF (1) +#define MICROPY_PY_SYS_MODULES (1) +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_PLATFORM "wasm" + +// IO is cooked and multiplexed via mp_hal_stdout_tx_strn in file.c +// so do not modify those +// https://github.com/python/cpython/blob/v3.7.3/Python/bltinmodule.c#L1849-L1932 + + +#define MICROPY_USE_INTERNAL_PRINTF (0) +#define MICROPY_PY_IO (1) // <=============== only 1 for wasm port +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_IO_RESOURCE_STREAM (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_IO_BYTESIO (1) +#define MICROPY_PY_IO_BUFFEREDWRITER (1) + +#define MICROPY_PY_SYS_STDIO_BUFFER (1) +#define MICROPY_PY_SYS_STDFILES (1) + +#if __EMSCRIPTEN__ + #define MICROPY_PY_OS_DUPTERM (1) // <==== or js console will get C stdout too ( C-OUT [spam] ) +#else + #if __ANDROID__ + #define MICROPY_PY_OS_DUPTERM (0) + #else + #define MICROPY_PY_OS_DUPTERM (1) + #endif +#endif + + +#define MICROPY_PY_THREAD (0) +#define MICROPY_PY_THREAD_GIL (0) +#define MICROPY_PY_TIME (0) + +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UERRNO (1) +#define MICROPY_PY_UERRNO_ERRORCODE (1) +#define MICROPY_PY_UHEAPQ (1) +#define MICROPY_PY_UHASHLIB (1) + +//F +#define MICROPY_PY_UHASHLIB_SHA1 (0) + +#define MICROPY_PY_UHASHLIB_SHA256 (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_UOS (1) +#define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_URE_SUB (1) + + +//TODO: need #define MICROPY_EVENT_POLL_HOOK + select_select on io demultiplexer. +//F +#define MICROPY_PY_USELECT (0) + + +#define MICROPY_PY_UTIME (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UZLIB (1) + +#define MICROPY_VFS (0) + +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_MPY_CODE_SAVE (1) +#define MICROPY_REPL_EVENT_DRIVEN (1) +#define MICROPY_ENABLE_DOC_STRING (1) + + +//asyncio implemented +#define MICROPY_ENABLE_SCHEDULER (0) +#define MICROPY_SCHEDULER_DEPTH (4) + + +// type definitions for the specific machine + +#define BYTES_PER_WORD (4) + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void*)((mp_uint_t)(p) | 1)) + +// This port is intended to be 32-bit, but unfortunately, int32_t for +// different targets may be defined in different ways - either as int +// or as long. This requires different printf formatting specifiers +// to print such value. So, we avoid int32_t and use int directly. +#define UINT_FMT "%u" +#define INT_FMT "%d" +typedef int mp_int_t; // must be pointer size +typedef unsigned mp_uint_t; // must be pointer size + +typedef long mp_off_t; + +#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) +//#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn(str, len) + + +extern const struct _mp_obj_module_t mp_module_time; +extern const struct _mp_obj_module_t mp_module_io; +extern const struct _mp_obj_module_t mp_module_os; + +#if MICROPY_PY_UOS_VFS + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos_vfs) }, +#else + #define MICROPY_PY_UOS_DEF { MP_ROM_QSTR(MP_QSTR_uos), (mp_obj_t)&mp_module_os }, +#endif + +#if MICROPY_PY_FFI + extern const struct _mp_obj_module_t mp_module_ffi; + #define MICROPY_PY_FFI_DEF { MP_ROM_QSTR(MP_QSTR_ffi), MP_ROM_PTR(&mp_module_ffi) }, +#else + #define MICROPY_PY_FFI_DEF +#endif + +#if MICROPY_PY_JNI + #define MICROPY_PY_JNI_DEF { MP_ROM_QSTR(MP_QSTR_jni), MP_ROM_PTR(&mp_module_jni) }, +#else + #define MICROPY_PY_JNI_DEF +#endif + +#if MICROPY_PY_TERMIOS + #define MICROPY_PY_TERMIOS_DEF { MP_ROM_QSTR(MP_QSTR_termios), MP_ROM_PTR(&mp_module_termios) }, +#else + #define MICROPY_PY_TERMIOS_DEF +#endif + +#if MICROPY_PY_SOCKET + #define MICROPY_PY_SOCKET_DEF { MP_ROM_QSTR(MP_QSTR_usocket), MP_ROM_PTR(&mp_module_socket) }, +#else + #define MICROPY_PY_SOCKET_DEF +#endif + +#if MICROPY_PY_USELECT_POSIX + #define MICROPY_PY_USELECT_DEF { MP_ROM_QSTR(MP_QSTR_uselect), MP_ROM_PTR(&mp_module_uselect) }, +#else + #define MICROPY_PY_USELECT_DEF +#endif + +#define MICROPY_PY_MACHINE_DEF + + +#if MICROPY_PY_LVGL + extern const struct _mp_obj_module_t mp_module_utime; + extern const struct _mp_obj_module_t mp_module_lvgl; + extern const struct _mp_obj_module_t mp_module_lvindev; + extern const struct _mp_obj_module_t mp_module_SDL; + + #include "lib/lv_bindings/lvgl/src/lv_misc/lv_gc.h" + + #define MICROPY_PY_LVGL_DEF \ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvgl), (mp_obj_t)&mp_module_lvgl },\ + { MP_OBJ_NEW_QSTR(MP_QSTR_lvindev), (mp_obj_t)&mp_module_lvindev},\ + { MP_OBJ_NEW_QSTR(MP_QSTR_SDL), (mp_obj_t)&mp_module_SDL }, + + #define MPR_void_mp_lv_user_data void *mp_lv_user_data; + #define MPR_LVGL LV_GC_ROOTS(LV_NO_PREFIX) +#else + #define MICROPY_PY_LVGL_DEF + #define MPR_void_mp_lv_user_data + #define MPR_LVGL +#endif + + +// { MP _ ROM_QSTR(MP_QSTR_umachine), MP _ ROM_PTR(&mp_module_machine) }, + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + MICROPY_PY_FFI_DEF \ + MICROPY_PY_JNI_DEF \ + MICROPY_PY_SOCKET_DEF \ + MICROPY_PY_MACHINE_DEF \ + MICROPY_PY_UOS_DEF \ + MICROPY_PY_USELECT_DEF \ + MICROPY_PY_TERMIOS_DEF \ + MICROPY_PY_LVGL_DEF \ + MODULES_LINKS + + + +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, \ + + + +#define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \ + { MP_OBJ_NEW_QSTR(MP_QSTR_binascii), (mp_obj_t)&mp_module_ubinascii }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_collections), (mp_obj_t)&mp_module_collections }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_errno), (mp_obj_t)&mp_module_uerrno }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_hashlib), (mp_obj_t)&mp_module_uhashlib }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_heapq), (mp_obj_t)&mp_module_uheapq }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_json), (mp_obj_t)&mp_module_ujson }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&mp_module_os }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_random), (mp_obj_t)&mp_module_urandom }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_re), (mp_obj_t)&mp_module_ure }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_select), (mp_obj_t)&mp_module_uselect }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_socket), (mp_obj_t)&mp_module_usocket }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_ssl), (mp_obj_t)&mp_module_ussl }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_struct), (mp_obj_t)&mp_module_ustruct }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&mp_module_time }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR_zlib), (mp_obj_t)&mp_module_uzlib }, \ + + + +// We need to provide a declaration/definition of alloca() +#include + +#define MICROPY_HW_BOARD_NAME "emscripten" +#define MICROPY_HW_MCU_NAME "wasm" + +#ifdef __linux__ +#define MICROPY_MIN_USE_STDOUT (1) +#endif + +#ifdef __thumb__ +#define MICROPY_MIN_USE_CORTEX_CPU (1) +#define MICROPY_MIN_USE_STM32_MCU (1) +#endif + +#define MP_STATE_PORT MP_STATE_VM + +#define MPR_const_char_readline_hist const char *readline_hist[16]; + +#define MICROPY_PORT_ROOT_POINTERS \ + MPR_LVGL \ + MPR_void_mp_lv_user_data \ + MPR_const_char_readline_hist \ + void *PyOS_InputHook; \ + int coro_call_counter; \ + + +#define FFCONF_H "lib/oofatfs/ffconf.h" + + + + + + + + +// diff --git a/ports/wapy-wasm/mphalport.h b/ports/wapy-wasm/mphalport.h new file mode 100644 index 000000000..e610f29f0 --- /dev/null +++ b/ports/wapy-wasm/mphalport.h @@ -0,0 +1,31 @@ +#include +//#include "py/nlr.h" +//#include "py/compile.h" +#include "py/runtime.h" +//#include "py/repl.h" +//#include "py/gc.h" +//#include "lib/utils/pyexec.h" + +#ifndef CHAR_CTRL_C +#define CHAR_CTRL_C (3) +#endif + + +//static inline void mp_hal_set_interrupt_char(char c) {} + +// TODO: wasm does not sleep +/* +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); } +#define mp_hal_ticks_cpu() 0 +*/ + +mp_uint_t mp_hal_ticks_ms(void); + + + +#define RAISE_ERRNO(err_flag, error_val) \ + { if (err_flag == -1) \ + { mp_raise_OSError(error_val); } } + + diff --git a/ports/wapy-wasm/qstr/modbuiltins.c b/ports/wapy-wasm/qstr/modbuiltins.c new file mode 100644 index 000000000..0b61780d3 --- /dev/null +++ b/ports/wapy-wasm/qstr/modbuiltins.c @@ -0,0 +1,777 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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/smallint.h" +#include "py/objint.h" +#include "py/objstr.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stream.h" + +#if MICROPY_PY_BUILTINS_FLOAT +#include +#endif + +#if MICROPY_PY_IO +extern struct _mp_dummy_t mp_sys_stdout_obj; // type is irrelevant, just need pointer +#endif + +// args[0] is function from class body +// args[1] is class name +// args[2:] are base objects +STATIC mp_obj_t mp_builtin___build_class__(size_t n_args, const mp_obj_t *args) { + assert(2 <= n_args); + + // set the new classes __locals__ object + mp_obj_dict_t *old_locals = mp_locals_get(); + mp_obj_t class_locals = mp_obj_new_dict(0); + mp_locals_set(MP_OBJ_TO_PTR(class_locals)); + + // call the class code + mp_obj_t cell = mp_call_function_0(args[0]); + + // restore old __locals__ object + mp_locals_set(old_locals); + + // get the class type (meta object) from the base objects + mp_obj_t meta; + if (n_args == 2) { + // no explicit bases, so use 'type' + meta = MP_OBJ_FROM_PTR(&mp_type_type); + } else { + // use type of first base object + meta = MP_OBJ_FROM_PTR(mp_obj_get_type(args[2])); + } + + // TODO do proper metaclass resolution for multiple base objects + + // create the new class using a call to the meta object + mp_obj_t meta_args[3]; + meta_args[0] = args[1]; // class name + meta_args[1] = mp_obj_new_tuple(n_args - 2, args + 2); // tuple of bases + meta_args[2] = class_locals; // dict of members + mp_obj_t new_class = mp_call_function_n_kw(meta, 3, 0, meta_args); + + // store into cell if neede + if (cell != mp_const_none) { + mp_obj_cell_set(cell, new_class); + } + + return new_class; +} +MP_DEFINE_CONST_FUN_OBJ_VAR(mp_builtin___build_class___obj, 2, mp_builtin___build_class__); + +STATIC mp_obj_t mp_builtin_abs(mp_obj_t o_in) { + return mp_unary_op(MP_UNARY_OP_ABS, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_abs_obj, mp_builtin_abs); + +STATIC mp_obj_t mp_builtin_all(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (!mp_obj_is_true(item)) { + return mp_const_false; + } + } + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_all_obj, mp_builtin_all); + +STATIC mp_obj_t mp_builtin_any(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (mp_obj_is_true(item)) { + return mp_const_true; + } + } + return mp_const_false; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_any_obj, mp_builtin_any); + +STATIC mp_obj_t mp_builtin_bin(mp_obj_t o_in) { + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_b_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_bin_obj, mp_builtin_bin); + +STATIC mp_obj_t mp_builtin_callable(mp_obj_t o_in) { + if (mp_obj_is_callable(o_in)) { + return mp_const_true; + } else { + return mp_const_false; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_callable_obj, mp_builtin_callable); + +STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_UNICODE + mp_uint_t c = mp_obj_get_int(o_in); + uint8_t str[4]; + int len = 0; + if (c < 0x80) { + *str = c; len = 1; + } else if (c < 0x800) { + str[0] = (c >> 6) | 0xC0; + str[1] = (c & 0x3F) | 0x80; + len = 2; + } else if (c < 0x10000) { + str[0] = (c >> 12) | 0xE0; + str[1] = ((c >> 6) & 0x3F) | 0x80; + str[2] = (c & 0x3F) | 0x80; + len = 3; + } else if (c < 0x110000) { + str[0] = (c >> 18) | 0xF0; + str[1] = ((c >> 12) & 0x3F) | 0x80; + str[2] = ((c >> 6) & 0x3F) | 0x80; + str[3] = (c & 0x3F) | 0x80; + len = 4; + } else { + mp_raise_ValueError("chr() arg not in range(0x110000)"); + } + return mp_obj_new_str_via_qstr((char*)str, len); + #else + mp_int_t ord = mp_obj_get_int(o_in); + if (0 <= ord && ord <= 0xff) { + uint8_t str[1] = {ord}; + return mp_obj_new_str_via_qstr((char*)str, 1); + } else { + mp_raise_ValueError("chr() arg not in range(256)"); + } + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr); + +STATIC mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { + mp_obj_t dir = mp_obj_new_list(0, NULL); + if (n_args == 0) { + // Make a list of names in the local namespace + mp_obj_dict_t *dict = mp_locals_get(); + for (size_t i = 0; i < dict->map.alloc; i++) { + if (mp_map_slot_is_filled(&dict->map, i)) { + mp_obj_list_append(dir, dict->map.table[i].key); + } + } + } else { // n_args == 1 + // Make a list of names in the given object + // Implemented by probing all possible qstrs with mp_load_method_maybe + size_t nqstr = QSTR_TOTAL(); + for (size_t i = MP_QSTR_ + 1; i < nqstr; ++i) { + mp_obj_t dest[2]; + mp_load_method_protected(args[0], i, dest, false); + if (dest[0] != MP_OBJ_NULL) { + #if MICROPY_PY_ALL_SPECIAL_METHODS + // Support for __dir__: see if we can dispatch to this special method + // This relies on MP_QSTR__dir__ being first after MP_QSTR_ + if (i == MP_QSTR___dir__ && dest[1] != MP_OBJ_NULL) { + return mp_call_method_n_kw(0, 0, dest); + } + #endif + mp_obj_list_append(dir, MP_OBJ_NEW_QSTR(i)); + } + } + } + return dir; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); + +STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { + return mp_binary_op(MP_BINARY_OP_DIVMOD, o1_in, o2_in); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_divmod_obj, mp_builtin_divmod); + +STATIC mp_obj_t mp_builtin_hash(mp_obj_t o_in) { + // result is guaranteed to be a (small) int + return mp_unary_op(MP_UNARY_OP_HASH, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hash_obj, mp_builtin_hash); + +STATIC mp_obj_t mp_builtin_hex(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_x), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_x_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hex_obj, mp_builtin_hex); + +#if MICROPY_PY_BUILTINS_INPUT + +#include "py/mphal.h" +#include "lib/mp-readline/readline.h" + +// A port can define mp_hal_readline if they want to use a custom function here +#ifndef mp_hal_readline +#define mp_hal_readline readline +#endif + +STATIC mp_obj_t mp_builtin_input(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_print(args[0], PRINT_STR); + } + vstr_t line; + vstr_init(&line, 16); + int ret = mp_hal_readline(&line, ""); + if (ret == CHAR_CTRL_C) { + nlr_raise(mp_obj_new_exception(&mp_type_KeyboardInterrupt)); + } + if (line.len == 0 && ret == CHAR_CTRL_D) { + nlr_raise(mp_obj_new_exception(&mp_type_EOFError)); + } + return mp_obj_new_str_from_vstr(&mp_type_str, &line); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_input_obj, 0, 1, mp_builtin_input); + +#endif + +STATIC mp_obj_t mp_builtin_iter(mp_obj_t o_in) { + return mp_getiter(o_in, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_iter_obj, mp_builtin_iter); + +#if MICROPY_PY_BUILTINS_MIN_MAX + +STATIC mp_obj_t mp_builtin_min_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs, mp_uint_t op) { + mp_map_elem_t *key_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_key), MP_MAP_LOOKUP); + mp_map_elem_t *default_elem; + mp_obj_t key_fn = key_elem == NULL ? MP_OBJ_NULL : key_elem->value; + if (n_args == 1) { + // given an iterable + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? item : mp_call_function_1(key_fn, item); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = item; + } + } + if (best_obj == MP_OBJ_NULL) { + default_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_default), MP_MAP_LOOKUP); + if (default_elem != NULL) { + best_obj = default_elem->value; + } else { + mp_raise_ValueError("arg is an empty sequence"); + } + } + return best_obj; + } else { + // given many args + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + for (size_t i = 0; i < n_args; i++) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? args[i] : mp_call_function_1(key_fn, args[i]); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = args[i]; + } + } + return best_obj; + } +} + +STATIC mp_obj_t mp_builtin_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_MORE); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_max_obj, 1, mp_builtin_max); + +STATIC mp_obj_t mp_builtin_min(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_LESS); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_min_obj, 1, mp_builtin_min); + +#endif + +#if MICROPY_PY_BUILTINS_NEXT2 +STATIC mp_obj_t mp_builtin_next(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_t ret = mpsl_iternext_allow_raise(args[0]); + if (ret == MP_OBJ_STOP_ITERATION) { + nlr_raise(mp_obj_new_exception(&mp_type_StopIteration)); + } else { + return ret; + } + } else { + mp_obj_t ret = mp_iternext(args[0]); + return ret == MP_OBJ_STOP_ITERATION ? args[1] : ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_next_obj, 1, 2, mp_builtin_next); +#else +STATIC mp_obj_t mp_builtin_next(mp_obj_t o) { + mp_obj_t ret = mpsl_iternext_allow_raise(o); + if (ret == MP_OBJ_STOP_ITERATION) { + nlr_raise(mp_obj_new_exception(&mp_type_StopIteration)); + } else { + return ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_next_obj, mp_builtin_next); +#endif + +STATIC mp_obj_t mp_builtin_oct(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_o), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_o_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_oct_obj, mp_builtin_oct); + +STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) { + size_t len; + const byte *str = (const byte*)mp_obj_str_get_data(o_in, &len); + #if MICROPY_PY_BUILTINS_STR_UNICODE + if (mp_obj_is_str(o_in)) { + len = utf8_charlen(str, len); + if (len == 1) { + return mp_obj_new_int(utf8_get_char(str)); + } + } else + #endif + { + // a bytes object, or a str without unicode support (don't sign extend the char) + if (len == 1) { + return MP_OBJ_NEW_SMALL_INT(str[0]); + } + } + + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("ord expects a character"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "ord() expected a character, but string of length %d found", (int)len)); + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_ord_obj, mp_builtin_ord); + +STATIC mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { + switch (n_args) { + case 2: return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); + default: +#if !MICROPY_PY_BUILTINS_POW3 + mp_raise_msg(&mp_type_NotImplementedError, "3-arg pow() not supported"); +#elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ + return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); +#else + return mp_obj_int_pow3(args[0], args[1], args[2]); +#endif + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_pow_obj, 2, 3, mp_builtin_pow); + +STATIC mp_obj_t mp_builtin_print(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_sep, ARG_end, ARG_file }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__space_)} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__0x0a_)} }, + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + { MP_QSTR_file, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_sys_stdout_obj)} }, + #endif + }; + + // parse args (a union is used to reduce the amount of C stack that is needed) + union { + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + size_t len[2]; + } u; + mp_arg_parse_all(0, NULL, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, u.args); + + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_get_stream_raise(u.args[ARG_file].u_obj, MP_STREAM_OP_WRITE); + mp_print_t print = {MP_OBJ_TO_PTR(u.args[ARG_file].u_obj), mp_stream_write_adaptor}; + #endif + + // extract the objects first because we are going to use the other part of the union + mp_obj_t sep = u.args[ARG_sep].u_obj; + mp_obj_t end = u.args[ARG_end].u_obj; + const char *sep_data = mp_obj_str_get_data(sep, &u.len[0]); + const char *end_data = mp_obj_str_get_data(end, &u.len[1]); + + for (size_t i = 0; i < n_args; i++) { + if (i > 0) { + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, sep_data, u.len[0]); + #else + mp_print_strn(&mp_plat_print, sep_data, u.len[0], 0, 0, 0); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_obj_print_helper(&print, pos_args[i], PRINT_STR); + #else + mp_obj_print_helper(&mp_plat_print, pos_args[i], PRINT_STR); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, end_data, u.len[1]); + #else + mp_print_strn(&mp_plat_print, end_data, u.len[1], 0, 0, 0); + #endif + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_print_obj, 0, mp_builtin_print); + +STATIC mp_obj_t mp_builtin___repl_print__(mp_obj_t o) { + if (o != mp_const_none) { + mp_obj_print_helper(MP_PYTHON_PRINTER, o, PRINT_REPR); + mp_print_str(MP_PYTHON_PRINTER, "\n"); + #if MICROPY_CAN_OVERRIDE_BUILTINS + // Set "_" special variable + mp_obj_t dest[2] = {MP_OBJ_SENTINEL, o}; + mp_type_module.attr(MP_OBJ_FROM_PTR(&mp_module_builtins), MP_QSTR__, dest); + #endif + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin___repl_print___obj, mp_builtin___repl_print__); + +STATIC mp_obj_t mp_builtin_repr(mp_obj_t o_in) { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + mp_obj_print_helper(&print, o_in, PRINT_REPR); + return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_repr_obj, mp_builtin_repr); + +STATIC mp_obj_t mp_builtin_round(size_t n_args, const mp_obj_t *args) { + mp_obj_t o_in = args[0]; + if (mp_obj_is_int(o_in)) { + if (n_args <= 1) { + return o_in; + } + + #if !MICROPY_PY_BUILTINS_ROUND_INT + mp_raise_NotImplementedError(NULL); + #else + mp_int_t num_dig = mp_obj_get_int(args[1]); + if (num_dig >= 0) { + return o_in; + } + + mp_obj_t mult = mp_binary_op(MP_BINARY_OP_POWER, MP_OBJ_NEW_SMALL_INT(10), MP_OBJ_NEW_SMALL_INT(-num_dig)); + mp_obj_t half_mult = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, mult, MP_OBJ_NEW_SMALL_INT(2)); + mp_obj_t modulo = mp_binary_op(MP_BINARY_OP_MODULO, o_in, mult); + mp_obj_t rounded = mp_binary_op(MP_BINARY_OP_SUBTRACT, o_in, modulo); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, half_mult, modulo))) { + return rounded; + } else if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, modulo, half_mult))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + // round to even number + mp_obj_t floor = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, o_in, mult); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_AND, floor, MP_OBJ_NEW_SMALL_INT(1)))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + return rounded; + } + } + #endif + } +#if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(o_in); + if (n_args > 1) { + mp_int_t num_dig = mp_obj_get_int(args[1]); + mp_float_t mult = MICROPY_FLOAT_C_FUN(pow)(10, num_dig); + // TODO may lead to overflow + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val * mult) / mult; + return mp_obj_new_float(rounded); + } + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val); + return mp_obj_new_int_from_float(rounded); +#else + mp_int_t r = mp_obj_get_int(o_in); + return mp_obj_new_int(r); +#endif +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_round_obj, 1, 2, mp_builtin_round); + +STATIC mp_obj_t mp_builtin_sum(size_t n_args, const mp_obj_t *args) { + mp_obj_t value; + switch (n_args) { + case 1: value = MP_OBJ_NEW_SMALL_INT(0); break; + default: value = args[1]; break; + } + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + value = mp_binary_op(MP_BINARY_OP_ADD, value, item); + } + return value; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_sum_obj, 1, 2, mp_builtin_sum); + +STATIC mp_obj_t mp_builtin_sorted(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args > 1) { + mp_raise_TypeError("must use keyword argument for key function"); + } + mp_obj_t self = mp_type_list.make_new(&mp_type_list, 1, 0, args); + mp_obj_list_sort(1, &self, kwargs); + + return self; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj, 1, mp_builtin_sorted); + +// See mp_load_attr() if making any changes +static inline mp_obj_t mp_load_attr_default(mp_obj_t base, qstr attr, mp_obj_t defval) { + mp_obj_t dest[2]; + // use load_method, raising or not raising exception + ((defval == MP_OBJ_NULL) ? mp_load_method : mp_load_method_maybe)(base, attr, dest); + if (dest[0] == MP_OBJ_NULL) { + return defval; + } else if (dest[1] == MP_OBJ_NULL) { + // load_method returned just a normal attribute + return dest[0]; + } else { + // load_method returned a method, so build a bound method object + return mp_obj_new_bound_meth(dest[0], dest[1]); + } +} + +STATIC mp_obj_t mp_builtin_getattr(size_t n_args, const mp_obj_t *args) { + mp_obj_t defval = MP_OBJ_NULL; + if (n_args > 2) { + defval = args[2]; + } + return mp_load_attr_default(args[0], mp_obj_str_get_qstr(args[1]), defval); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_getattr_obj, 2, 3, mp_builtin_getattr); + +STATIC mp_obj_t mp_builtin_setattr(mp_obj_t base, mp_obj_t attr, mp_obj_t value) { + mp_store_attr(base, mp_obj_str_get_qstr(attr), value); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(mp_builtin_setattr_obj, mp_builtin_setattr); + +#if MICROPY_CPYTHON_COMPAT +STATIC mp_obj_t mp_builtin_delattr(mp_obj_t base, mp_obj_t attr) { + return mp_builtin_setattr(base, attr, MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_delattr_obj, mp_builtin_delattr); +#endif + +STATIC mp_obj_t mp_builtin_hasattr(mp_obj_t object_in, mp_obj_t attr_in) { + qstr attr = mp_obj_str_get_qstr(attr_in); + mp_obj_t dest[2]; + mp_load_method_protected(object_in, attr, dest, false); + return mp_obj_new_bool(dest[0] != MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_hasattr_obj, mp_builtin_hasattr); + +STATIC mp_obj_t mp_builtin_globals(void) { + return MP_OBJ_FROM_PTR(mp_globals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_globals_obj, mp_builtin_globals); + +STATIC mp_obj_t mp_builtin_locals(void) { + return MP_OBJ_FROM_PTR(mp_locals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_locals_obj, mp_builtin_locals); + +// These are defined in terms of MicroPython API functions right away +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_id_obj, mp_obj_id); +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_len_obj, mp_obj_len); + +STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_builtins) }, + + // built-in core functions + { MP_ROM_QSTR(MP_QSTR___build_class__), MP_ROM_PTR(&mp_builtin___build_class___obj) }, + { MP_ROM_QSTR(MP_QSTR___import__), MP_ROM_PTR(&mp_builtin___import___obj) }, + { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + + // built-in types + { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_PTR(&mp_type_bool) }, + { MP_ROM_QSTR(MP_QSTR_bytes), MP_ROM_PTR(&mp_type_bytes) }, + #if MICROPY_PY_BUILTINS_BYTEARRAY + { MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&mp_type_bytearray) }, + #endif + #if MICROPY_PY_BUILTINS_COMPLEX + { MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_PTR(&mp_type_complex) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dict), MP_ROM_PTR(&mp_type_dict) }, + #if MICROPY_PY_BUILTINS_ENUMERATE + { MP_ROM_QSTR(MP_QSTR_enumerate), MP_ROM_PTR(&mp_type_enumerate) }, + #endif + #if MICROPY_PY_BUILTINS_FILTER + { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&mp_type_filter) }, + #endif + #if MICROPY_PY_BUILTINS_FLOAT + { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_PTR(&mp_type_float) }, + #endif + #if MICROPY_PY_BUILTINS_SET && MICROPY_PY_BUILTINS_FROZENSET + { MP_ROM_QSTR(MP_QSTR_frozenset), MP_ROM_PTR(&mp_type_frozenset) }, + #endif + { MP_ROM_QSTR(MP_QSTR_int), MP_ROM_PTR(&mp_type_int) }, + { MP_ROM_QSTR(MP_QSTR_list), MP_ROM_PTR(&mp_type_list) }, + { MP_ROM_QSTR(MP_QSTR_map), MP_ROM_PTR(&mp_type_map) }, + #if MICROPY_PY_BUILTINS_MEMORYVIEW + { MP_ROM_QSTR(MP_QSTR_memoryview), MP_ROM_PTR(&mp_type_memoryview) }, + #endif + { MP_ROM_QSTR(MP_QSTR_object), MP_ROM_PTR(&mp_type_object) }, + #if MICROPY_PY_BUILTINS_PROPERTY + { MP_ROM_QSTR(MP_QSTR_property), MP_ROM_PTR(&mp_type_property) }, + #endif + { MP_ROM_QSTR(MP_QSTR_range), MP_ROM_PTR(&mp_type_range) }, + #if MICROPY_PY_BUILTINS_REVERSED + { MP_ROM_QSTR(MP_QSTR_reversed), MP_ROM_PTR(&mp_type_reversed) }, + #endif + #if MICROPY_PY_BUILTINS_SET + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&mp_type_set) }, + #endif + #if MICROPY_PY_BUILTINS_SLICE + { MP_ROM_QSTR(MP_QSTR_slice), MP_ROM_PTR(&mp_type_slice) }, + #endif + { MP_ROM_QSTR(MP_QSTR_str), MP_ROM_PTR(&mp_type_str) }, + { MP_ROM_QSTR(MP_QSTR_super), MP_ROM_PTR(&mp_type_super) }, + { MP_ROM_QSTR(MP_QSTR_tuple), MP_ROM_PTR(&mp_type_tuple) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&mp_type_type) }, + { MP_ROM_QSTR(MP_QSTR_zip), MP_ROM_PTR(&mp_type_zip) }, + + { MP_ROM_QSTR(MP_QSTR_classmethod), MP_ROM_PTR(&mp_type_classmethod) }, + { MP_ROM_QSTR(MP_QSTR_staticmethod), MP_ROM_PTR(&mp_type_staticmethod) }, + + // built-in objects + { MP_ROM_QSTR(MP_QSTR_Ellipsis), MP_ROM_PTR(&mp_const_ellipsis_obj) }, + #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED + { MP_ROM_QSTR(MP_QSTR_NotImplemented), MP_ROM_PTR(&mp_const_notimplemented_obj) }, + #endif + + // built-in user functions + { MP_ROM_QSTR(MP_QSTR_abs), MP_ROM_PTR(&mp_builtin_abs_obj) }, + { MP_ROM_QSTR(MP_QSTR_all), MP_ROM_PTR(&mp_builtin_all_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&mp_builtin_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_bin), MP_ROM_PTR(&mp_builtin_bin_obj) }, + { MP_ROM_QSTR(MP_QSTR_callable), MP_ROM_PTR(&mp_builtin_callable_obj) }, + #if MICROPY_PY_BUILTINS_COMPILE + { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mp_builtin_compile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_chr), MP_ROM_PTR(&mp_builtin_chr_obj) }, + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_delattr), MP_ROM_PTR(&mp_builtin_delattr_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dir), MP_ROM_PTR(&mp_builtin_dir_obj) }, + { MP_ROM_QSTR(MP_QSTR_divmod), MP_ROM_PTR(&mp_builtin_divmod_obj) }, + #if MICROPY_PY_BUILTINS_EVAL_EXEC + { MP_ROM_QSTR(MP_QSTR_eval), MP_ROM_PTR(&mp_builtin_eval_obj) }, + { MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&mp_builtin_exec_obj) }, + #endif + #if MICROPY_PY_BUILTINS_EXECFILE + { MP_ROM_QSTR(MP_QSTR_execfile), MP_ROM_PTR(&mp_builtin_execfile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_getattr), MP_ROM_PTR(&mp_builtin_getattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_setattr), MP_ROM_PTR(&mp_builtin_setattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_globals), MP_ROM_PTR(&mp_builtin_globals_obj) }, + { MP_ROM_QSTR(MP_QSTR_hasattr), MP_ROM_PTR(&mp_builtin_hasattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_hash), MP_ROM_PTR(&mp_builtin_hash_obj) }, + #if MICROPY_PY_BUILTINS_HELP + { MP_ROM_QSTR(MP_QSTR_help), MP_ROM_PTR(&mp_builtin_help_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_hex), MP_ROM_PTR(&mp_builtin_hex_obj) }, + { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&mp_builtin_id_obj) }, + #if MICROPY_PY_BUILTINS_INPUT + { MP_ROM_QSTR(MP_QSTR_input), MP_ROM_PTR(&mp_builtin_input_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_isinstance), MP_ROM_PTR(&mp_builtin_isinstance_obj) }, + { MP_ROM_QSTR(MP_QSTR_issubclass), MP_ROM_PTR(&mp_builtin_issubclass_obj) }, + { MP_ROM_QSTR(MP_QSTR_iter), MP_ROM_PTR(&mp_builtin_iter_obj) }, + { MP_ROM_QSTR(MP_QSTR_len), MP_ROM_PTR(&mp_builtin_len_obj) }, + { MP_ROM_QSTR(MP_QSTR_locals), MP_ROM_PTR(&mp_builtin_locals_obj) }, + #if MICROPY_PY_BUILTINS_MIN_MAX + { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&mp_builtin_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&mp_builtin_min_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_next), MP_ROM_PTR(&mp_builtin_next_obj) }, + { MP_ROM_QSTR(MP_QSTR_oct), MP_ROM_PTR(&mp_builtin_oct_obj) }, + { MP_ROM_QSTR(MP_QSTR_ord), MP_ROM_PTR(&mp_builtin_ord_obj) }, + { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_builtin_pow_obj) }, + { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) }, + { MP_ROM_QSTR(MP_QSTR_repr), MP_ROM_PTR(&mp_builtin_repr_obj) }, + { MP_ROM_QSTR(MP_QSTR_round), MP_ROM_PTR(&mp_builtin_round_obj) }, + { MP_ROM_QSTR(MP_QSTR_sorted), MP_ROM_PTR(&mp_builtin_sorted_obj) }, + { MP_ROM_QSTR(MP_QSTR_sum), MP_ROM_PTR(&mp_builtin_sum_obj) }, + + // built-in exceptions + { MP_ROM_QSTR(MP_QSTR_BaseException), MP_ROM_PTR(&mp_type_BaseException) }, + { MP_ROM_QSTR(MP_QSTR_ArithmeticError), MP_ROM_PTR(&mp_type_ArithmeticError) }, + { MP_ROM_QSTR(MP_QSTR_AssertionError), MP_ROM_PTR(&mp_type_AssertionError) }, + { MP_ROM_QSTR(MP_QSTR_AttributeError), MP_ROM_PTR(&mp_type_AttributeError) }, + { MP_ROM_QSTR(MP_QSTR_EOFError), MP_ROM_PTR(&mp_type_EOFError) }, + { MP_ROM_QSTR(MP_QSTR_Exception), MP_ROM_PTR(&mp_type_Exception) }, + { MP_ROM_QSTR(MP_QSTR_GeneratorExit), MP_ROM_PTR(&mp_type_GeneratorExit) }, + { MP_ROM_QSTR(MP_QSTR_ImportError), MP_ROM_PTR(&mp_type_ImportError) }, + { MP_ROM_QSTR(MP_QSTR_IndentationError), MP_ROM_PTR(&mp_type_IndentationError) }, + { MP_ROM_QSTR(MP_QSTR_IndexError), MP_ROM_PTR(&mp_type_IndexError) }, + { MP_ROM_QSTR(MP_QSTR_KeyboardInterrupt), MP_ROM_PTR(&mp_type_KeyboardInterrupt) }, + { MP_ROM_QSTR(MP_QSTR_KeyError), MP_ROM_PTR(&mp_type_KeyError) }, + { MP_ROM_QSTR(MP_QSTR_LookupError), MP_ROM_PTR(&mp_type_LookupError) }, + { MP_ROM_QSTR(MP_QSTR_MemoryError), MP_ROM_PTR(&mp_type_MemoryError) }, + { MP_ROM_QSTR(MP_QSTR_NameError), MP_ROM_PTR(&mp_type_NameError) }, + { MP_ROM_QSTR(MP_QSTR_NotImplementedError), MP_ROM_PTR(&mp_type_NotImplementedError) }, + { MP_ROM_QSTR(MP_QSTR_OSError), MP_ROM_PTR(&mp_type_OSError) }, + { MP_ROM_QSTR(MP_QSTR_OverflowError), MP_ROM_PTR(&mp_type_OverflowError) }, + { MP_ROM_QSTR(MP_QSTR_RuntimeError), MP_ROM_PTR(&mp_type_RuntimeError) }, + #if MICROPY_PY_ASYNC_AWAIT + { MP_ROM_QSTR(MP_QSTR_StopAsyncIteration), MP_ROM_PTR(&mp_type_StopAsyncIteration) }, + #endif + { MP_ROM_QSTR(MP_QSTR_StopIteration), MP_ROM_PTR(&mp_type_StopIteration) }, + { MP_ROM_QSTR(MP_QSTR_SyntaxError), MP_ROM_PTR(&mp_type_SyntaxError) }, + { MP_ROM_QSTR(MP_QSTR_SystemExit), MP_ROM_PTR(&mp_type_SystemExit) }, + { MP_ROM_QSTR(MP_QSTR_TypeError), MP_ROM_PTR(&mp_type_TypeError) }, + #if MICROPY_PY_BUILTINS_STR_UNICODE + { MP_ROM_QSTR(MP_QSTR_UnicodeError), MP_ROM_PTR(&mp_type_UnicodeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ValueError), MP_ROM_PTR(&mp_type_ValueError) }, + #if MICROPY_EMIT_NATIVE + { MP_ROM_QSTR(MP_QSTR_ViperTypeError), MP_ROM_PTR(&mp_type_ViperTypeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ZeroDivisionError), MP_ROM_PTR(&mp_type_ZeroDivisionError) }, + // Somehow CPython managed to have OverflowError not inherit from ValueError ;-/ + // TODO: For MICROPY_CPYTHON_COMPAT==0 use ValueError to avoid exc proliferation + + // Extra builtins as defined by a port + MICROPY_PORT_BUILTINS +}; + +MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table); + +const mp_obj_module_t mp_module_builtins = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_builtins_globals, +}; diff --git a/ports/wapy-wasm/qstr/objtype.c b/ports/wapy-wasm/qstr/objtype.c new file mode 100644 index 000000000..3e65a32f0 --- /dev/null +++ b/ports/wapy-wasm/qstr/objtype.c @@ -0,0 +1,1405 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2018 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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 "py/objtype.h" +#include "py/runtime.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_PRINT (0) +#define DEBUG_printf(...) (void)0 +#endif + +#define ENABLE_SPECIAL_ACCESSORS \ + (MICROPY_PY_DESCRIPTORS || MICROPY_PY_DELATTR_SETATTR || MICROPY_PY_BUILTINS_PROPERTY) + +#define TYPE_FLAG_IS_SUBCLASSED (0x0001) +#define TYPE_FLAG_HAS_SPECIAL_ACCESSORS (0x0002) + +STATIC mp_obj_t static_class_method_make_new(const mp_obj_type_t *self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); + +/******************************************************************************/ +// instance object + +STATIC int instance_count_native_bases(const mp_obj_type_t *type, const mp_obj_type_t **last_native_base) { + int count = 0; + for (;;) { + if (type == &mp_type_object) { + // Not a "real" type, end search here. + return count; + } else if (mp_obj_is_native_type(type)) { + // Native types don't have parents (at least not from our perspective) so end. + *last_native_base = type; + return count + 1; + } else if (type->parent == NULL) { + // No parents so end search here. + return count; + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) { + // Multiple parents, search through them all recursively. + const mp_obj_tuple_t *parent_tuple = type->parent; + const mp_obj_t *item = parent_tuple->items; + const mp_obj_t *top = item + parent_tuple->len; + for (; item < top; ++item) { + assert(mp_obj_is_type(*item, &mp_type_type)); + const mp_obj_type_t *bt = (const mp_obj_type_t *)MP_OBJ_TO_PTR(*item); + count += instance_count_native_bases(bt, last_native_base); + } + return count; + #endif + } else { + // A single parent, use iteration to continue the search. + type = type->parent; + } + } +} + +// This wrapper function is allows a subclass of a native type to call the +// __init__() method (corresponding to type->make_new) of the native type. +STATIC mp_obj_t native_base_init_wrapper(size_t n_args, const mp_obj_t *args) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(args[0]); + const mp_obj_type_t *native_base = NULL; + instance_count_native_bases(self->base.type, &native_base); + self->subobj[0] = native_base->make_new(native_base, n_args - 1, 0, args + 1); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(native_base_init_wrapper_obj, 1, MP_OBJ_FUN_ARGS_MAX, native_base_init_wrapper); + +#if !MICROPY_CPYTHON_COMPAT +STATIC +#endif +mp_obj_instance_t *mp_obj_new_instance(const mp_obj_type_t *class, const mp_obj_type_t **native_base) { + size_t num_native_bases = instance_count_native_bases(class, native_base); + assert(num_native_bases < 2); + mp_obj_instance_t *o = m_new_obj_var(mp_obj_instance_t, mp_obj_t, num_native_bases); + o->base.type = class; + mp_map_init(&o->members, 0); + // Initialise the native base-class slot (should be 1 at most) with a valid + // object. It doesn't matter which object, so long as it can be uniquely + // distinguished from a native class that is initialised. + if (num_native_bases != 0) { + o->subobj[0] = MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj); + } + return o; +} + +// TODO +// This implements depth-first left-to-right MRO, which is not compliant with Python3 MRO +// http://python-history.blogspot.com/2010/06/method-resolution-order.html +// https://www.python.org/download/releases/2.3/mro/ +// +// will keep lookup->dest[0]'s value (should be MP_OBJ_NULL on invocation) if attribute +// is not found +// will set lookup->dest[0] to MP_OBJ_SENTINEL if special method was found in a native +// type base via slot id (as specified by lookup->meth_offset). As there can be only one +// native base, it's known that it applies to instance->subobj[0]. In most cases, we also +// don't need to know which type it was - because instance->subobj[0] is of that type. +// The only exception is when object is not yet constructed, then we need to know base +// native type to construct its instance->subobj[0] from. But this case is handled via +// instance_count_native_bases(), which returns a native base which it saw. +struct class_lookup_data { + mp_obj_instance_t *obj; + qstr attr; + size_t meth_offset; + mp_obj_t *dest; + bool is_type; +}; + +STATIC void mp_obj_class_lookup(struct class_lookup_data *lookup, const mp_obj_type_t *type) { + assert(lookup->dest[0] == MP_OBJ_NULL); + assert(lookup->dest[1] == MP_OBJ_NULL); + for (;;) { + DEBUG_printf("mp_obj_class_lookup: Looking up %s in %s\n", qstr_str(lookup->attr), qstr_str(type->name)); + // Optimize special method lookup for native types + // This avoids extra method_name => slot lookup. On the other hand, + // this should not be applied to class types, as will result in extra + // lookup either. + if (lookup->meth_offset != 0 && mp_obj_is_native_type(type)) { + if (*(void**)((char*)type + lookup->meth_offset) != NULL) { + DEBUG_printf("mp_obj_class_lookup: Matched special meth slot (off=%d) for %s\n", + lookup->meth_offset, qstr_str(lookup->attr)); + lookup->dest[0] = MP_OBJ_SENTINEL; + return; + } + } + + if (type->locals_dict != NULL) { + // search locals_dict (the set of methods/attributes) + assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &type->locals_dict->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(lookup->attr), MP_MAP_LOOKUP); + if (elem != NULL) { + if (lookup->is_type) { + // If we look up a class method, we need to return original type for which we + // do a lookup, not a (base) type in which we found the class method. + const mp_obj_type_t *org_type = (const mp_obj_type_t*)lookup->obj; + mp_convert_member_lookup(MP_OBJ_NULL, org_type, elem->value, lookup->dest); + } else { + mp_obj_instance_t *obj = lookup->obj; + mp_obj_t obj_obj; + if (obj != NULL && mp_obj_is_native_type(type) && type != &mp_type_object /* object is not a real type */) { + // If we're dealing with native base class, then it applies to native sub-object + obj_obj = obj->subobj[0]; + } else { + obj_obj = MP_OBJ_FROM_PTR(obj); + } + mp_convert_member_lookup(obj_obj, type, elem->value, lookup->dest); + } +#if DEBUG_PRINT + DEBUG_printf("mp_obj_class_lookup: Returning: "); + mp_obj_print_helper(MICROPY_DEBUG_PRINTER, lookup->dest[0], PRINT_REPR); + if (lookup->dest[1] != MP_OBJ_NULL) { + // Don't try to repr() lookup->dest[1], as we can be called recursively + DEBUG_printf(" <%s @%p>", mp_obj_get_type_str(lookup->dest[1]), MP_OBJ_TO_PTR(lookup->dest[1])); + } + DEBUG_printf("\n"); +#endif + return; + } + } + + // Previous code block takes care about attributes defined in .locals_dict, + // but some attributes of native types may be handled using .load_attr method, + // so make sure we try to lookup those too. + if (lookup->obj != NULL && !lookup->is_type && mp_obj_is_native_type(type) && type != &mp_type_object /* object is not a real type */) { + mp_load_method_maybe(lookup->obj->subobj[0], lookup->attr, lookup->dest); + if (lookup->dest[0] != MP_OBJ_NULL) { + return; + } + } + + // attribute not found, keep searching base classes + + if (type->parent == NULL) { + DEBUG_printf("mp_obj_class_lookup: No more parents\n"); + return; + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) { + const mp_obj_tuple_t *parent_tuple = type->parent; + const mp_obj_t *item = parent_tuple->items; + const mp_obj_t *top = item + parent_tuple->len - 1; + for (; item < top; ++item) { + assert(mp_obj_is_type(*item, &mp_type_type)); + mp_obj_type_t *bt = (mp_obj_type_t*)MP_OBJ_TO_PTR(*item); + if (bt == &mp_type_object) { + // Not a "real" type + continue; + } + mp_obj_class_lookup(lookup, bt); + if (lookup->dest[0] != MP_OBJ_NULL) { + return; + } + } + + // search last base (simple tail recursion elimination) + assert(mp_obj_is_type(*item, &mp_type_type)); + type = (mp_obj_type_t*)MP_OBJ_TO_PTR(*item); + #endif + } else { + type = type->parent; + } + if (type == &mp_type_object) { + // Not a "real" type + return; + } + } +} + +STATIC void instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + qstr meth = (kind == PRINT_STR) ? MP_QSTR___str__ : MP_QSTR___repr__; + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = meth, + .meth_offset = offsetof(mp_obj_type_t, print), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_NULL && kind == PRINT_STR) { + // If there's no __str__, fall back to __repr__ + lookup.attr = MP_QSTR___repr__; + lookup.meth_offset = 0; + mp_obj_class_lookup(&lookup, self->base.type); + } + + if (member[0] == MP_OBJ_SENTINEL) { + // Handle Exception subclasses specially + if (mp_obj_is_native_exception_instance(self->subobj[0])) { + if (kind != PRINT_STR) { + mp_print_str(print, qstr_str(self->base.type->name)); + } + mp_obj_print_helper(print, self->subobj[0], kind | PRINT_EXC_SUBCLASS); + } else { + mp_obj_print_helper(print, self->subobj[0], kind); + } + return; + } + + if (member[0] != MP_OBJ_NULL) { + mp_obj_t r = mp_call_function_1(member[0], self_in); + mp_obj_print_helper(print, r, PRINT_STR); + return; + } + + // TODO: CPython prints fully-qualified type name + mp_printf(print, "<%s object at %p>", mp_obj_get_type_str(self_in), self); +} + +mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_instance_type(self)); + + // look for __new__ function + mp_obj_t init_fn[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = NULL, + .attr = MP_QSTR___new__, + .meth_offset = offsetof(mp_obj_type_t, make_new), + .dest = init_fn, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self); + + const mp_obj_type_t *native_base = NULL; + mp_obj_instance_t *o; + if (init_fn[0] == MP_OBJ_NULL || init_fn[0] == MP_OBJ_SENTINEL) { + // Either there is no __new__() method defined or there is a native + // constructor. In both cases create a blank instance. + o = mp_obj_new_instance(self, &native_base); + + // Since type->make_new() implements both __new__() and __init__() in + // one go, of which the latter may be overridden by the Python subclass, + // we defer (see the end of this function) the call of the native + // constructor to give a chance for the Python __init__() method to call + // said native constructor. + + } else { + // Call Python class __new__ function with all args to create an instance + mp_obj_t new_ret; + if (n_args == 0 && n_kw == 0) { + mp_obj_t args2[1] = {MP_OBJ_FROM_PTR(self)}; + new_ret = mp_call_function_n_kw(init_fn[0], 1, 0, args2); + } else { + mp_obj_t *args2 = m_new(mp_obj_t, 1 + n_args + 2 * n_kw); + args2[0] = MP_OBJ_FROM_PTR(self); + memcpy(args2 + 1, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); + new_ret = mp_call_function_n_kw(init_fn[0], n_args + 1, n_kw, args2); + m_del(mp_obj_t, args2, 1 + n_args + 2 * n_kw); + } + + // https://docs.python.org/3.4/reference/datamodel.html#object.__new__ + // "If __new__() does not return an instance of cls, then the new + // instance's __init__() method will not be invoked." + if (mp_obj_get_type(new_ret) != self) { + return new_ret; + } + + // The instance returned by __new__() becomes the new object + o = MP_OBJ_TO_PTR(new_ret); + } + + // now call Python class __init__ function with all args + // This method has a chance to call super().__init__() to construct a + // possible native base class. + init_fn[0] = init_fn[1] = MP_OBJ_NULL; + lookup.obj = o; + lookup.attr = MP_QSTR___init__; + lookup.meth_offset = 0; + mp_obj_class_lookup(&lookup, self); + if (init_fn[0] != MP_OBJ_NULL) { + mp_obj_t init_ret; + if (n_args == 0 && n_kw == 0) { + init_ret = mp_call_method_n_kw(0, 0, init_fn); + } else { + mp_obj_t *args2 = m_new(mp_obj_t, 2 + n_args + 2 * n_kw); + args2[0] = init_fn[0]; + args2[1] = init_fn[1]; + memcpy(args2 + 2, args, (n_args + 2 * n_kw) * sizeof(mp_obj_t)); + init_ret = mp_call_method_n_kw(n_args, n_kw, args2); + m_del(mp_obj_t, args2, 2 + n_args + 2 * n_kw); + } + if (init_ret != mp_const_none) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("__init__() should return None"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret))); + } + } + + } + + // If the type had a native base that was not explicitly initialised + // (constructed) by the Python __init__() method then construct it now. + if (native_base != NULL && o->subobj[0] == MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj)) { + o->subobj[0] = native_base->make_new(native_base, n_args, n_kw, args); + } + + return MP_OBJ_FROM_PTR(o); +} + +// Qstrs for special methods are guaranteed to have a small value, so we use byte +// type to represent them. +const byte mp_unary_op_method_name[MP_UNARY_OP_NUM_RUNTIME] = { + [MP_UNARY_OP_BOOL] = MP_QSTR___bool__, + [MP_UNARY_OP_LEN] = MP_QSTR___len__, + [MP_UNARY_OP_HASH] = MP_QSTR___hash__, + [MP_UNARY_OP_INT] = MP_QSTR___int__, + #if MICROPY_PY_ALL_SPECIAL_METHODS + [MP_UNARY_OP_POSITIVE] = MP_QSTR___pos__, + [MP_UNARY_OP_NEGATIVE] = MP_QSTR___neg__, + [MP_UNARY_OP_INVERT] = MP_QSTR___invert__, + [MP_UNARY_OP_ABS] = MP_QSTR___abs__, + #endif + #if MICROPY_PY_SYS_GETSIZEOF + [MP_UNARY_OP_SIZEOF] = MP_QSTR___sizeof__, + #endif +}; + +STATIC mp_obj_t instance_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + + #if MICROPY_PY_SYS_GETSIZEOF + if (MP_UNLIKELY(op == MP_UNARY_OP_SIZEOF)) { + // TODO: This doesn't count inherited objects (self->subobj) + const mp_obj_type_t *native_base; + size_t num_native_bases = instance_count_native_bases(mp_obj_get_type(self_in), &native_base); + + size_t sz = sizeof(*self) + sizeof(*self->subobj) * num_native_bases + + sizeof(*self->members.table) * self->members.alloc; + return MP_OBJ_NEW_SMALL_INT(sz); + } + #endif + + qstr op_name = mp_unary_op_method_name[op]; + /* Still try to lookup native slot + if (op_name == 0) { + return MP_OBJ_NULL; + } + */ + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = op_name, + .meth_offset = offsetof(mp_obj_type_t, unary_op), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_SENTINEL) { + return mp_unary_op(op, self->subobj[0]); + } else if (member[0] != MP_OBJ_NULL) { + mp_obj_t val = mp_call_function_1(member[0], self_in); + + switch (op) { + case MP_UNARY_OP_HASH: + // __hash__ must return a small int + val = MP_OBJ_NEW_SMALL_INT(mp_obj_get_int_truncated(val)); + break; + case MP_UNARY_OP_INT: + // Must return int + if (!mp_obj_is_int(val)) { + mp_raise_TypeError(NULL); + } + break; + default: + // No need to do anything + ; + } + return val; + } else { + if (op == MP_UNARY_OP_HASH) { + lookup.attr = MP_QSTR___eq__; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_NULL) { + // https://docs.python.org/3/reference/datamodel.html#object.__hash__ + // "User-defined classes have __eq__() and __hash__() methods by default; + // with them, all objects compare unequal (except with themselves) and + // x.__hash__() returns an appropriate value such that x == y implies + // both that x is y and hash(x) == hash(y)." + return MP_OBJ_NEW_SMALL_INT((mp_uint_t)self_in); + } + // "A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None. + // When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError" + } + + return MP_OBJ_NULL; // op not supported + } +} + +// Binary-op enum values not listed here will have the default value of 0 in the +// table, corresponding to MP_QSTR_NULL, and are therefore unsupported (a lookup will +// fail). They can be added at the expense of code size for the qstr. +// Qstrs for special methods are guaranteed to have a small value, so we use byte +// type to represent them. +const byte mp_binary_op_method_name[MP_BINARY_OP_NUM_RUNTIME] = { + [MP_BINARY_OP_LESS] = MP_QSTR___lt__, + [MP_BINARY_OP_MORE] = MP_QSTR___gt__, + [MP_BINARY_OP_EQUAL] = MP_QSTR___eq__, + [MP_BINARY_OP_LESS_EQUAL] = MP_QSTR___le__, + [MP_BINARY_OP_MORE_EQUAL] = MP_QSTR___ge__, + // MP_BINARY_OP_NOT_EQUAL, // a != b calls a == b and inverts result + [MP_BINARY_OP_CONTAINS] = MP_QSTR___contains__, + + // If an inplace method is not found a normal method will be used as a fallback + [MP_BINARY_OP_INPLACE_ADD] = MP_QSTR___iadd__, + [MP_BINARY_OP_INPLACE_SUBTRACT] = MP_QSTR___isub__, + #if MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS + [MP_BINARY_OP_INPLACE_MULTIPLY] = MP_QSTR___imul__, + [MP_BINARY_OP_INPLACE_FLOOR_DIVIDE] = MP_QSTR___ifloordiv__, + [MP_BINARY_OP_INPLACE_TRUE_DIVIDE] = MP_QSTR___itruediv__, + [MP_BINARY_OP_INPLACE_MODULO] = MP_QSTR___imod__, + [MP_BINARY_OP_INPLACE_POWER] = MP_QSTR___ipow__, + [MP_BINARY_OP_INPLACE_OR] = MP_QSTR___ior__, + [MP_BINARY_OP_INPLACE_XOR] = MP_QSTR___ixor__, + [MP_BINARY_OP_INPLACE_AND] = MP_QSTR___iand__, + [MP_BINARY_OP_INPLACE_LSHIFT] = MP_QSTR___ilshift__, + [MP_BINARY_OP_INPLACE_RSHIFT] = MP_QSTR___irshift__, + #endif + + [MP_BINARY_OP_ADD] = MP_QSTR___add__, + [MP_BINARY_OP_SUBTRACT] = MP_QSTR___sub__, + #if MICROPY_PY_ALL_SPECIAL_METHODS + [MP_BINARY_OP_MULTIPLY] = MP_QSTR___mul__, + [MP_BINARY_OP_FLOOR_DIVIDE] = MP_QSTR___floordiv__, + [MP_BINARY_OP_TRUE_DIVIDE] = MP_QSTR___truediv__, + [MP_BINARY_OP_MODULO] = MP_QSTR___mod__, + [MP_BINARY_OP_DIVMOD] = MP_QSTR___divmod__, + [MP_BINARY_OP_POWER] = MP_QSTR___pow__, + [MP_BINARY_OP_OR] = MP_QSTR___or__, + [MP_BINARY_OP_XOR] = MP_QSTR___xor__, + [MP_BINARY_OP_AND] = MP_QSTR___and__, + [MP_BINARY_OP_LSHIFT] = MP_QSTR___lshift__, + [MP_BINARY_OP_RSHIFT] = MP_QSTR___rshift__, + #endif + + #if MICROPY_PY_REVERSE_SPECIAL_METHODS + [MP_BINARY_OP_REVERSE_ADD] = MP_QSTR___radd__, + [MP_BINARY_OP_REVERSE_SUBTRACT] = MP_QSTR___rsub__, + #if MICROPY_PY_ALL_SPECIAL_METHODS + [MP_BINARY_OP_REVERSE_MULTIPLY] = MP_QSTR___rmul__, + [MP_BINARY_OP_REVERSE_FLOOR_DIVIDE] = MP_QSTR___rfloordiv__, + [MP_BINARY_OP_REVERSE_TRUE_DIVIDE] = MP_QSTR___rtruediv__, + [MP_BINARY_OP_REVERSE_MODULO] = MP_QSTR___rmod__, + [MP_BINARY_OP_REVERSE_POWER] = MP_QSTR___rpow__, + [MP_BINARY_OP_REVERSE_OR] = MP_QSTR___ror__, + [MP_BINARY_OP_REVERSE_XOR] = MP_QSTR___rxor__, + [MP_BINARY_OP_REVERSE_AND] = MP_QSTR___rand__, + [MP_BINARY_OP_REVERSE_LSHIFT] = MP_QSTR___rlshift__, + [MP_BINARY_OP_REVERSE_RSHIFT] = MP_QSTR___rrshift__, + #endif + #endif +}; + +STATIC mp_obj_t instance_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + // Note: For ducktyping, CPython does not look in the instance members or use + // __getattr__ or __getattribute__. It only looks in the class dictionary. + mp_obj_instance_t *lhs = MP_OBJ_TO_PTR(lhs_in); +retry:; + qstr op_name = mp_binary_op_method_name[op]; + /* Still try to lookup native slot + if (op_name == 0) { + return MP_OBJ_NULL; + } + */ + mp_obj_t dest[3] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = lhs, + .attr = op_name, + .meth_offset = offsetof(mp_obj_type_t, binary_op), + .dest = dest, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, lhs->base.type); + + mp_obj_t res; + if (dest[0] == MP_OBJ_SENTINEL) { + res = mp_binary_op(op, lhs->subobj[0], rhs_in); + } else if (dest[0] != MP_OBJ_NULL) { + dest[2] = rhs_in; + res = mp_call_method_n_kw(1, 0, dest); + } else { + // If this was an inplace method, fallback to normal method + // https://docs.python.org/3/reference/datamodel.html#object.__iadd__ : + // "If a specific method is not defined, the augmented assignment + // falls back to the normal methods." + if (op >= MP_BINARY_OP_INPLACE_OR && op <= MP_BINARY_OP_INPLACE_POWER) { + op -= MP_BINARY_OP_INPLACE_OR - MP_BINARY_OP_OR; + goto retry; + } + return MP_OBJ_NULL; // op not supported + } + + #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED + // NotImplemented means "try other fallbacks (like calling __rop__ + // instead of __op__) and if nothing works, raise TypeError". As + // MicroPython doesn't implement any fallbacks, signal to raise + // TypeError right away. + if (res == mp_const_notimplemented) { + return MP_OBJ_NULL; // op not supported + } + #endif + + return res; +} + +STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + // logic: look in instance members then class locals + assert(mp_obj_is_instance_type(mp_obj_get_type(self_in))); + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + + mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); + if (elem != NULL) { + // object member, always treated as a value + dest[0] = elem->value; + return; + } +#if MICROPY_CPYTHON_COMPAT + if (attr == MP_QSTR___dict__) { + // Create a new dict with a copy of the instance's map items. + // This creates, unlike CPython, a 'read-only' __dict__: modifying + // it will not result in modifications to the actual instance members. + mp_map_t *map = &self->members; + mp_obj_t attr_dict = mp_obj_new_dict(map->used); + for (size_t i = 0; i < map->alloc; ++i) { + if (mp_map_slot_is_filled(map, i)) { + mp_obj_dict_store(attr_dict, map->table[i].key, map->table[i].value); + } + } + dest[0] = attr_dict; + return; + } +#endif + struct class_lookup_data lookup = { + .obj = self, + .attr = attr, + .meth_offset = 0, + .dest = dest, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + mp_obj_t member = dest[0]; + if (member != MP_OBJ_NULL) { + if (!(self->base.type->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + // Class doesn't have any special accessors to check so return straightaway + return; + } + + #if MICROPY_PY_BUILTINS_PROPERTY + if (mp_obj_is_type(member, &mp_type_property)) { + // object member is a property; delegate the load to the property + // Note: This is an optimisation for code size and execution time. + // The proper way to do it is have the functionality just below + // in a __get__ method of the property object, and then it would + // be called by the descriptor code down below. But that way + // requires overhead for the nested mp_call's and overhead for + // the code. + const mp_obj_t *proxy = mp_obj_property_get(member); + if (proxy[0] == mp_const_none) { + mp_raise_msg(&mp_type_AttributeError, "unreadable attribute"); + } else { + dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); + } + return; + } + #endif + + #if MICROPY_PY_DESCRIPTORS + // found a class attribute; if it has a __get__ method then call it with the + // class instance and class as arguments and return the result + // Note that this is functionally correct but very slow: each load_attr + // requires an extra mp_load_method_maybe to check for the __get__. + mp_obj_t attr_get_method[4]; + mp_load_method_maybe(member, MP_QSTR___get__, attr_get_method); + if (attr_get_method[0] != MP_OBJ_NULL) { + attr_get_method[2] = self_in; + attr_get_method[3] = MP_OBJ_FROM_PTR(mp_obj_get_type(self_in)); + dest[0] = mp_call_method_n_kw(2, 0, attr_get_method); + } + #endif + return; + } + + // try __getattr__ + if (attr != MP_QSTR___getattr__) { + #if MICROPY_PY_DELATTR_SETATTR + // If the requested attr is __setattr__/__delattr__ then don't delegate the lookup + // to __getattr__. If we followed CPython's behaviour then __setattr__/__delattr__ + // would have already been found in the "object" base class. + if (attr == MP_QSTR___setattr__ || attr == MP_QSTR___delattr__) { + return; + } + #endif + + mp_obj_t dest2[3]; + mp_load_method_maybe(self_in, MP_QSTR___getattr__, dest2); + if (dest2[0] != MP_OBJ_NULL) { + // __getattr__ exists, call it and return its result + dest2[2] = MP_OBJ_NEW_QSTR(attr); + dest[0] = mp_call_method_n_kw(1, 0, dest2); + return; + } + } +} + +STATIC bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + + if (!(self->base.type->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + // Class doesn't have any special accessors so skip their checks + goto skip_special_accessors; + } + + #if MICROPY_PY_BUILTINS_PROPERTY || MICROPY_PY_DESCRIPTORS + // With property and/or descriptors enabled we need to do a lookup + // first in the class dict for the attribute to see if the store should + // be delegated. + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = attr, + .meth_offset = 0, + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + + if (member[0] != MP_OBJ_NULL) { + #if MICROPY_PY_BUILTINS_PROPERTY + if (mp_obj_is_type(member[0], &mp_type_property)) { + // attribute exists and is a property; delegate the store/delete + // Note: This is an optimisation for code size and execution time. + // The proper way to do it is have the functionality just below in + // a __set__/__delete__ method of the property object, and then it + // would be called by the descriptor code down below. But that way + // requires overhead for the nested mp_call's and overhead for + // the code. + const mp_obj_t *proxy = mp_obj_property_get(member[0]); + mp_obj_t dest[2] = {self_in, value}; + if (value == MP_OBJ_NULL) { + // delete attribute + if (proxy[2] == mp_const_none) { + // TODO better error message? + return false; + } else { + mp_call_function_n_kw(proxy[2], 1, 0, dest); + return true; + } + } else { + // store attribute + if (proxy[1] == mp_const_none) { + // TODO better error message? + return false; + } else { + mp_call_function_n_kw(proxy[1], 2, 0, dest); + return true; + } + } + } + #endif + + #if MICROPY_PY_DESCRIPTORS + // found a class attribute; if it has a __set__/__delete__ method then + // call it with the class instance (and value) as arguments + if (value == MP_OBJ_NULL) { + // delete attribute + mp_obj_t attr_delete_method[3]; + mp_load_method_maybe(member[0], MP_QSTR___delete__, attr_delete_method); + if (attr_delete_method[0] != MP_OBJ_NULL) { + attr_delete_method[2] = self_in; + mp_call_method_n_kw(1, 0, attr_delete_method); + return true; + } + } else { + // store attribute + mp_obj_t attr_set_method[4]; + mp_load_method_maybe(member[0], MP_QSTR___set__, attr_set_method); + if (attr_set_method[0] != MP_OBJ_NULL) { + attr_set_method[2] = self_in; + attr_set_method[3] = value; + mp_call_method_n_kw(2, 0, attr_set_method); + return true; + } + } + #endif + } + #endif + + #if MICROPY_PY_DELATTR_SETATTR + if (value == MP_OBJ_NULL) { + // delete attribute + // try __delattr__ first + mp_obj_t attr_delattr_method[3]; + mp_load_method_maybe(self_in, MP_QSTR___delattr__, attr_delattr_method); + if (attr_delattr_method[0] != MP_OBJ_NULL) { + // __delattr__ exists, so call it + attr_delattr_method[2] = MP_OBJ_NEW_QSTR(attr); + mp_call_method_n_kw(1, 0, attr_delattr_method); + return true; + } + } else { + // store attribute + // try __setattr__ first + mp_obj_t attr_setattr_method[4]; + mp_load_method_maybe(self_in, MP_QSTR___setattr__, attr_setattr_method); + if (attr_setattr_method[0] != MP_OBJ_NULL) { + // __setattr__ exists, so call it + attr_setattr_method[2] = MP_OBJ_NEW_QSTR(attr); + attr_setattr_method[3] = value; + mp_call_method_n_kw(2, 0, attr_setattr_method); + return true; + } + } + #endif + +skip_special_accessors: + + if (value == MP_OBJ_NULL) { + // delete attribute + mp_map_elem_t *elem = mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); + return elem != NULL; + } else { + // store attribute + mp_map_lookup(&self->members, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + return true; + } +} + +STATIC void mp_obj_instance_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + mp_obj_instance_load_attr(self_in, attr, dest); + } else { + if (mp_obj_instance_store_attr(self_in, attr, dest[1])) { + dest[0] = MP_OBJ_NULL; // indicate success + } + } +} + +STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t member[4] = {MP_OBJ_NULL, MP_OBJ_NULL, index, value}; + struct class_lookup_data lookup = { + .obj = self, + .meth_offset = offsetof(mp_obj_type_t, subscr), + .dest = member, + .is_type = false, + }; + if (value == MP_OBJ_NULL) { + // delete item + lookup.attr = MP_QSTR___delitem__; + } else if (value == MP_OBJ_SENTINEL) { + // load item + lookup.attr = MP_QSTR___getitem__; + } else { + // store item + lookup.attr = MP_QSTR___setitem__; + } + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_SENTINEL) { + return mp_obj_subscr(self->subobj[0], index, value); + } else if (member[0] != MP_OBJ_NULL) { + size_t n_args = value == MP_OBJ_NULL || value == MP_OBJ_SENTINEL ? 1 : 2; + mp_obj_t ret = mp_call_method_n_kw(n_args, 0, member); + if (value == MP_OBJ_SENTINEL) { + return ret; + } else { + return mp_const_none; + } + } else { + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t mp_obj_instance_get_call(mp_obj_t self_in, mp_obj_t *member) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + struct class_lookup_data lookup = { + .obj = self, + .attr = MP_QSTR___call__, + .meth_offset = offsetof(mp_obj_type_t, call), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + return member[0]; +} + +bool mp_obj_instance_is_callable(mp_obj_t self_in) { + mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; + return mp_obj_instance_get_call(self_in, member) != MP_OBJ_NULL; +} + +mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_t member[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; + mp_obj_t call = mp_obj_instance_get_call(self_in, member); + if (call == MP_OBJ_NULL) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("object not callable"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%s' object isn't callable", mp_obj_get_type_str(self_in))); + } + } + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + if (call == MP_OBJ_SENTINEL) { + return mp_call_function_n_kw(self->subobj[0], n_args, n_kw, args); + } + + return mp_call_method_self_n_kw(member[0], member[1], n_args, n_kw, args); +} + +STATIC mp_obj_t instance_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = MP_QSTR___iter__, + .meth_offset = offsetof(mp_obj_type_t, getiter), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } else if (member[0] == MP_OBJ_SENTINEL) { + mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); + return type->getiter(self->subobj[0], iter_buf); + } else { + return mp_call_method_n_kw(0, 0, member); + } +} + +STATIC mp_int_t instance_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t member[2] = {MP_OBJ_NULL}; + struct class_lookup_data lookup = { + .obj = self, + .attr = MP_QSTR_, // don't actually look for a method + .meth_offset = offsetof(mp_obj_type_t, buffer_p.get_buffer), + .dest = member, + .is_type = false, + }; + mp_obj_class_lookup(&lookup, self->base.type); + if (member[0] == MP_OBJ_SENTINEL) { + mp_obj_type_t *type = mp_obj_get_type(self->subobj[0]); + return type->buffer_p.get_buffer(self->subobj[0], bufinfo, flags); + } else { + return 1; // object does not support buffer protocol + } +} + +/******************************************************************************/ +// type object +// - the struct is mp_obj_type_t and is defined in obj.h so const types can be made +// - there is a constant mp_obj_type_t (called mp_type_type) for the 'type' object +// - creating a new class (a new type) creates a new mp_obj_type_t + +#if ENABLE_SPECIAL_ACCESSORS +STATIC bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) { + #if MICROPY_PY_DELATTR_SETATTR + if (key == MP_OBJ_NEW_QSTR(MP_QSTR___setattr__) || key == MP_OBJ_NEW_QSTR(MP_QSTR___delattr__)) { + return true; + } + #endif + #if MICROPY_PY_BUILTINS_PROPERTY + if (mp_obj_is_type(value, &mp_type_property)) { + return true; + } + #endif + #if MICROPY_PY_DESCRIPTORS + static const uint8_t to_check[] = { + MP_QSTR___get__, MP_QSTR___set__, MP_QSTR___delete__, + }; + for (size_t i = 0; i < MP_ARRAY_SIZE(to_check); ++i) { + mp_obj_t dest_temp[2]; + mp_load_method_protected(value, to_check[i], dest_temp, true); + if (dest_temp[0] != MP_OBJ_NULL) { + return true; + } + } + #endif + return false; +} +#endif + +STATIC void type_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->name); +} + +STATIC mp_obj_t type_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + + mp_arg_check_num(n_args, n_kw, 1, 3, false); + + switch (n_args) { + case 1: + return MP_OBJ_FROM_PTR(mp_obj_get_type(args[0])); + + case 3: + // args[0] = name + // args[1] = bases tuple + // args[2] = locals dict + return mp_obj_new_type(mp_obj_str_get_qstr(args[0]), args[1], args[2]); + + default: + mp_raise_TypeError("type takes 1 or 3 arguments"); + } +} + +STATIC mp_obj_t type_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // instantiate an instance of a class + + mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->make_new == NULL) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("cannot create instance"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "cannot create '%q' instances", self->name)); + } + } + + // make new instance + mp_obj_t o = self->make_new(self, n_args, n_kw, args); + + // return new instance + return o; +} + +STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + assert(mp_obj_is_type(self_in, &mp_type_type)); + mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); + + if (dest[0] == MP_OBJ_NULL) { + // load attribute + #if MICROPY_CPYTHON_COMPAT + if (attr == MP_QSTR___name__) { + dest[0] = MP_OBJ_NEW_QSTR(self->name); + return; + } + #endif + struct class_lookup_data lookup = { + .obj = (mp_obj_instance_t*)self, + .attr = attr, + .meth_offset = 0, + .dest = dest, + .is_type = true, + }; + mp_obj_class_lookup(&lookup, self); + } else { + // delete/store attribute + + if (self->locals_dict != NULL) { + assert(self->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &self->locals_dict->map; + if (locals_map->is_fixed) { + // can't apply delete/store to a fixed map + return; + } + if (dest[1] == MP_OBJ_NULL) { + // delete attribute + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND); + if (elem != NULL) { + dest[0] = MP_OBJ_NULL; // indicate success + } + } else { + #if ENABLE_SPECIAL_ACCESSORS + // Check if we add any special accessor methods with this store + if (!(self->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + if (check_for_special_accessors(MP_OBJ_NEW_QSTR(attr), dest[1])) { + if (self->flags & TYPE_FLAG_IS_SUBCLASSED) { + // This class is already subclassed so can't have special accessors added + mp_raise_msg(&mp_type_AttributeError, "can't add special method to already-subclassed class"); + } + self->flags |= TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + } + } + #endif + + // store attribute + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + elem->value = dest[1]; + dest[0] = MP_OBJ_NULL; // indicate success + } + } + } +} + +const mp_obj_type_t mp_type_type = { + { &mp_type_type }, + .name = MP_QSTR_type, + .print = type_print, + .make_new = type_make_new, + .call = type_call, + .unary_op = mp_generic_unary_op, + .attr = type_attr, +}; + +mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) { + // Verify input objects have expected type + if (!mp_obj_is_type(bases_tuple, &mp_type_tuple)) { + mp_raise_TypeError(NULL); + } + if (!mp_obj_is_type(locals_dict, &mp_type_dict)) { + mp_raise_TypeError(NULL); + } + + // TODO might need to make a copy of locals_dict; at least that's how CPython does it + + // Basic validation of base classes + uint16_t base_flags = 0; + size_t bases_len; + mp_obj_t *bases_items; + mp_obj_tuple_get(bases_tuple, &bases_len, &bases_items); + for (size_t i = 0; i < bases_len; i++) { + if (!mp_obj_is_type(bases_items[i], &mp_type_type)) { + mp_raise_TypeError(NULL); + } + mp_obj_type_t *t = MP_OBJ_TO_PTR(bases_items[i]); + // TODO: Verify with CPy, tested on function type + if (t->make_new == NULL) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("type isn't an acceptable base type"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "type '%q' isn't an acceptable base type", t->name)); + } + } + #if ENABLE_SPECIAL_ACCESSORS + if (mp_obj_is_instance_type(t)) { + t->flags |= TYPE_FLAG_IS_SUBCLASSED; + base_flags |= t->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + } + #endif + } + + mp_obj_type_t *o = m_new0(mp_obj_type_t, 1); + o->base.type = &mp_type_type; + o->flags = base_flags; + o->name = name; + o->print = instance_print; + o->make_new = mp_obj_instance_make_new; + o->call = mp_obj_instance_call; + o->unary_op = instance_unary_op; + o->binary_op = instance_binary_op; + o->attr = mp_obj_instance_attr; + o->subscr = instance_subscr; + o->getiter = instance_getiter; + //o->iternext = ; not implemented + o->buffer_p.get_buffer = instance_get_buffer; + + if (bases_len > 0) { + // Inherit protocol from a base class. This allows to define an + // abstract base class which would translate C-level protocol to + // Python method calls, and any subclass inheriting from it will + // support this feature. + o->protocol = ((mp_obj_type_t*)MP_OBJ_TO_PTR(bases_items[0]))->protocol; + + if (bases_len >= 2) { + #if MICROPY_MULTIPLE_INHERITANCE + o->parent = MP_OBJ_TO_PTR(bases_tuple); + #else + mp_raise_NotImplementedError("multiple inheritance not supported"); + #endif + } else { + o->parent = MP_OBJ_TO_PTR(bases_items[0]); + } + } + + o->locals_dict = MP_OBJ_TO_PTR(locals_dict); + + #if ENABLE_SPECIAL_ACCESSORS + // Check if the class has any special accessor methods + if (!(o->flags & TYPE_FLAG_HAS_SPECIAL_ACCESSORS)) { + for (size_t i = 0; i < o->locals_dict->map.alloc; i++) { + if (mp_map_slot_is_filled(&o->locals_dict->map, i)) { + const mp_map_elem_t *elem = &o->locals_dict->map.table[i]; + if (check_for_special_accessors(elem->key, elem->value)) { + o->flags |= TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + break; + } + } + } + } + #endif + + const mp_obj_type_t *native_base; + size_t num_native_bases = instance_count_native_bases(o, &native_base); + if (num_native_bases > 1) { + mp_raise_TypeError("multiple bases have instance lay-out conflict"); + } + + mp_map_t *locals_map = &o->locals_dict->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(MP_QSTR___new__), MP_MAP_LOOKUP); + if (elem != NULL) { + // __new__ slot exists; check if it is a function + if (mp_obj_is_fun(elem->value)) { + // __new__ is a function, wrap it in a staticmethod decorator + elem->value = static_class_method_make_new(&mp_type_staticmethod, 1, 0, &elem->value); + } + } + + return MP_OBJ_FROM_PTR(o); +} + +/******************************************************************************/ +// super object + +typedef struct _mp_obj_super_t { + mp_obj_base_t base; + mp_obj_t type; + mp_obj_t obj; +} mp_obj_super_t; + +STATIC void super_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_super_t *self = MP_OBJ_TO_PTR(self_in); + mp_print_str(print, "type, PRINT_STR); + mp_print_str(print, ", "); + mp_obj_print_helper(print, self->obj, PRINT_STR); + mp_print_str(print, ">"); +} + +STATIC mp_obj_t super_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + // 0 arguments are turned into 2 in the compiler + // 1 argument is not yet implemented + mp_arg_check_num(n_args, n_kw, 2, 2, false); + if (!mp_obj_is_type(args[0], &mp_type_type)) { + mp_raise_TypeError(NULL); + } + mp_obj_super_t *o = m_new_obj(mp_obj_super_t); + *o = (mp_obj_super_t){{type_in}, args[0], args[1]}; + return MP_OBJ_FROM_PTR(o); +} + +STATIC void super_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + // not load attribute + return; + } + + assert(mp_obj_is_type(self_in, &mp_type_super)); + mp_obj_super_t *self = MP_OBJ_TO_PTR(self_in); + + assert(mp_obj_is_type(self->type, &mp_type_type)); + + mp_obj_type_t *type = MP_OBJ_TO_PTR(self->type); + + struct class_lookup_data lookup = { + .obj = MP_OBJ_TO_PTR(self->obj), + .attr = attr, + .meth_offset = 0, + .dest = dest, + .is_type = false, + }; + + // Allow a call super().__init__() to reach any native base classes + if (attr == MP_QSTR___init__) { + lookup.meth_offset = offsetof(mp_obj_type_t, make_new); + } + + if (type->parent == NULL) { + // no parents, do nothing + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)type->parent)->type == &mp_type_tuple) { + const mp_obj_tuple_t *parent_tuple = type->parent; + size_t len = parent_tuple->len; + const mp_obj_t *items = parent_tuple->items; + for (size_t i = 0; i < len; i++) { + assert(mp_obj_is_type(items[i], &mp_type_type)); + if (MP_OBJ_TO_PTR(items[i]) == &mp_type_object) { + // The "object" type will be searched at the end of this function, + // and we don't want to lookup native methods in object. + continue; + } + mp_obj_class_lookup(&lookup, (mp_obj_type_t*)MP_OBJ_TO_PTR(items[i])); + if (dest[0] != MP_OBJ_NULL) { + break; + } + } + #endif + } else if (type->parent != &mp_type_object) { + mp_obj_class_lookup(&lookup, type->parent); + } + + if (dest[0] != MP_OBJ_NULL) { + if (dest[0] == MP_OBJ_SENTINEL) { + // Looked up native __init__ so defer to it + dest[0] = MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj); + dest[1] = self->obj; + } + return; + } + + // Reset meth_offset so we don't look up any native methods in object, + // because object never takes up the native base-class slot. + lookup.meth_offset = 0; + + mp_obj_class_lookup(&lookup, &mp_type_object); +} + +const mp_obj_type_t mp_type_super = { + { &mp_type_type }, + .name = MP_QSTR_super, + .print = super_print, + .make_new = super_make_new, + .attr = super_attr, +}; + +void mp_load_super_method(qstr attr, mp_obj_t *dest) { + mp_obj_super_t super = {{&mp_type_super}, dest[1], dest[2]}; + mp_load_method(MP_OBJ_FROM_PTR(&super), attr, dest); +} + +/******************************************************************************/ +// subclassing and built-ins specific to types + +// object and classinfo should be type objects +// (but the function will fail gracefully if they are not) +bool mp_obj_is_subclass_fast(mp_const_obj_t object, mp_const_obj_t classinfo) { + for (;;) { + if (object == classinfo) { + return true; + } + + // not equivalent classes, keep searching base classes + + // object should always be a type object, but just return false if it's not + if (!mp_obj_is_type(object, &mp_type_type)) { + return false; + } + + const mp_obj_type_t *self = MP_OBJ_TO_PTR(object); + + if (self->parent == NULL) { + // type has no parents + return false; + #if MICROPY_MULTIPLE_INHERITANCE + } else if (((mp_obj_base_t*)self->parent)->type == &mp_type_tuple) { + // get the base objects (they should be type objects) + const mp_obj_tuple_t *parent_tuple = self->parent; + const mp_obj_t *item = parent_tuple->items; + const mp_obj_t *top = item + parent_tuple->len - 1; + + // iterate through the base objects + for (; item < top; ++item) { + if (mp_obj_is_subclass_fast(*item, classinfo)) { + return true; + } + } + + // search last base (simple tail recursion elimination) + object = *item; + #endif + } else { + // type has 1 parent + object = MP_OBJ_FROM_PTR(self->parent); + } + } +} + +STATIC mp_obj_t mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) { + size_t len; + mp_obj_t *items; + if (mp_obj_is_type(classinfo, &mp_type_type)) { + len = 1; + items = &classinfo; + } else if (mp_obj_is_type(classinfo, &mp_type_tuple)) { + mp_obj_tuple_get(classinfo, &len, &items); + } else { + mp_raise_TypeError("issubclass() arg 2 must be a class or a tuple of classes"); + } + + for (size_t i = 0; i < len; i++) { + // We explicitly check for 'object' here since no-one explicitly derives from it + if (items[i] == MP_OBJ_FROM_PTR(&mp_type_object) || mp_obj_is_subclass_fast(object, items[i])) { + return mp_const_true; + } + } + return mp_const_false; +} + +STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) { + if (!mp_obj_is_type(object, &mp_type_type)) { + mp_raise_TypeError("issubclass() arg 1 must be a class"); + } + return mp_obj_is_subclass(object, classinfo); +} + +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_issubclass_obj, mp_builtin_issubclass); + +STATIC mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) { + return mp_obj_is_subclass(MP_OBJ_FROM_PTR(mp_obj_get_type(object)), classinfo); +} + +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance); + +mp_obj_t mp_instance_cast_to_native_base(mp_const_obj_t self_in, mp_const_obj_t native_type) { + mp_obj_type_t *self_type = mp_obj_get_type(self_in); + if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(self_type), native_type)) { + return MP_OBJ_NULL; + } + mp_obj_instance_t *self = (mp_obj_instance_t*)MP_OBJ_TO_PTR(self_in); + return self->subobj[0]; +} + +/******************************************************************************/ +// staticmethod and classmethod types (probably should go in a different file) + +STATIC mp_obj_t static_class_method_make_new(const mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(self == &mp_type_staticmethod || self == &mp_type_classmethod); + + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_static_class_method_t *o = m_new_obj(mp_obj_static_class_method_t); + *o = (mp_obj_static_class_method_t){{self}, args[0]}; + return MP_OBJ_FROM_PTR(o); +} + +const mp_obj_type_t mp_type_staticmethod = { + { &mp_type_type }, + .name = MP_QSTR_staticmethod, + .make_new = static_class_method_make_new, +}; + +const mp_obj_type_t mp_type_classmethod = { + { &mp_type_type }, + .name = MP_QSTR_classmethod, + .make_new = static_class_method_make_new, +}; diff --git a/ports/wapy-wasm/qstrdefsport.h b/ports/wapy-wasm/qstrdefsport.h new file mode 120000 index 000000000..6cb761a72 --- /dev/null +++ b/ports/wapy-wasm/qstrdefsport.h @@ -0,0 +1 @@ +../wapy/qstrdefsport.h \ No newline at end of file diff --git a/ports/wapy-wasm/upython.c b/ports/wapy-wasm/upython.c new file mode 100644 index 000000000..5312303b4 --- /dev/null +++ b/ports/wapy-wasm/upython.c @@ -0,0 +1,280 @@ +// +// core/main.c +// core/vfs.c + +#define HEAP_SIZE 64 * 1024 * 1024 + +// TODO: use a circular buffer for everything io related +#define MP_IO_SHM_SIZE 65535 + +#define MP_IO_SIZE 512 + +#define IO_KBD ( MP_IO_SHM_SIZE - (1 * MP_IO_SIZE) ) + + +#if MICROPY_ENABLE_PYSTACK +//#define MP_STACK_SIZE 16384 +#define MP_STACK_SIZE 32768 +static mp_obj_t pystack[MP_STACK_SIZE]; +#endif + +static char *stack_top; + +#include "../wapy/upython.h" + +#include "../wapy/core/ringbuf_o.h" +#include "../wapy/core/ringbuf_b.h" + +//static +struct wPyInterpreterState i_main; +//static +struct wPyThreadState i_state; + +RBB_T(out_rbb, 16384); + + + +EMSCRIPTEN_KEEPALIVE int +show_os_loop(int state) { + int last = SHOW_OS_LOOP; + if (state >= 0) { + SHOW_OS_LOOP = state; + if (state > 0) { + fprintf(stdout, "------------- showing os loop / starting repl --------------\n"); + repl_started = 1; + } else { + if (last != state) + fprintf(stdout, "------------- hiding os loop --------------\n"); + } + } + return (last > 0); +} + +EMSCRIPTEN_KEEPALIVE int +state_os_loop(int state) { + static int last_state = -666; + + if (!state) { + last_state = SHOW_OS_LOOP; + SHOW_OS_LOOP = 0; + } else { + if (last_state != -666) + SHOW_OS_LOOP = last_state; + last_state = -666; + } + return last_state; +} + +// ---- + + +// should check null +char * +shm_ptr() { + return &i_main.shm_stdio[0]; +} + +char * +shm_get_ptr(int major, int minor) { + // keyboards + if (major == IO_KBD) { + if (minor == 0) + return &i_main.shm_stdio[IO_KBD]; + } + return NULL; +} + + + +char * +Py_NewInterpreter() { + i_main.shm_stdio = (char *) malloc(MP_IO_SHM_SIZE); + if (!i_main.shm_stdio) + fprintf(stdout, "74:shm_stdio malloc failed\n"); + i_main.shm_stdio[0] = 0; + for (int i = 0; i < MP_IO_SHM_SIZE; i++) + i_main.shm_stdio[i] = 0; + i_state.interp = &i_main; + pyexec_event_repl_init(); + return shm_ptr(); +} + +void +Py_Initialize() { + int stack_dummy; + stack_top = (char *) &stack_dummy; + +#if MICROPY_ENABLE_GC + char *heap = (char *) malloc(HEAP_SIZE * sizeof(char)); + gc_init(heap, heap + HEAP_SIZE); +#endif + +//#if MICROPY_ENABLE_PYSTACK + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); +//#endif + + mp_init(); + +#if MICROPY_EMIT_NATIVE + // Set default emitter options + MP_STATE_VM(default_emit_opt) = emit_opt; +#else + //(void)emit_opt; +#endif + + + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); + mp_obj_list_init(mp_sys_argv, 0); +} + +// TODO py/stackctrl.c#L40 check stack way + + +#if !MICROPY_ENABLE_FINALISER +#error requires MICROPY_ENABLE_FINALISER (1) +#endif + + +#undef gc_collect + + + + +//extern void gc_collect_root(void **ptrs, size_t len); + +#include "py/gc.h" + +#undef gc_collect + + +// GC boundaries + + +// The address of the top of the stack when we first saw it +// This approximates a pointer to the bottom of the stack, but +// may not necessarily _be_ the exact bottom. This is set by +// the entry point of the application +static void *stack_initial = NULL; +// Pointer to the end of the stack +static uintptr_t stack_max = (uintptr_t) NULL; +// The current stack pointer +static uintptr_t stack_ptr_val = (uintptr_t) NULL; +// The amount of stack remaining +static ptrdiff_t stack_left = 0; + +// Maximum stack size of 248k +// This limit is arbitrary, but is what works for me under Node.js when compiled with emscripten +static size_t stack_limit = 1024 * 248 * 1; + + +void +gc_collect(void) { + + gc_dump_info(); + + stack_ptr_val = (uintptr_t) __builtin_frame_address(0); + + clog("gc_collect_start"); + + gc_collect_start(); + + size_t bottom = (uintptr_t) stack_ptr_val; + + void **ptrs = (void **) (void *) stack_ptr_val; + + size_t len = ((uintptr_t) stack_initial - bottom) / sizeof(uintptr_t); + + clog("gc_collect stack_initial=%p bottom=%zu len=%zu", stack_initial, bottom, len); + +//343 + + gc_collect_root(ptrs, len); +//343 + +#if MICROPY_PY_THREAD + mp_thread_gc_others(); +#endif +#if MICROPY_EMIT_NATIVE + mp_unix_mark_exec(); +#endif + + clog("gc_collect_end"); + gc_collect_end(); +} + +// #endif // gc + +int +pyeval(const char *src, mp_parse_input_kind_t input_kind) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), False); + + if (lex == NULL) { + fprintf(stdout, "148:NULL LEXER->handle_uncaught_exception\n%s\n", src); + return 0; + } + { +#if MICROPY_ENABLE_COMPILER + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); + if (module_fun != MP_OBJ_NULL) { + mp_obj_t ret = mp_call_function_0(module_fun); + if (ret != MP_OBJ_NULL) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + + } else { + return 1; //success + } + } + + } else { + // uncaught exception + fprintf(stdout, "150:NULL FUNCTION\n"); + } +#else + #pragma message "compiler is disabled, no repl" +#endif + } + return 0; +} + +#if __EMSCRIPTEN__ + #include "../wapy/wasm_mphal.c" +#endif + +#if __ANDROID__ + #include "../wapy/aosp_mphal.c" +#endif + +int +PyRun_IO_CODE() { + return pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); +} + +int +PyRun_SimpleString(const char *command) { + int retval = 0; + if (command) { + retval = pyeval(command, MP_PARSE_FILE_INPUT); + } else { + if (i_main.shm_stdio[0]) { + retval = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); + i_main.shm_stdio[0] = 0; + } + } + return retval; +} + + +EMSCRIPTEN_KEEPALIVE int +repl_run(int warmup) { + if (warmup == 1) + return MP_IO_SHM_SIZE; + //wPy_NewInterpreter(); + repl_started = MP_IO_SHM_SIZE; + return 1; +} diff --git a/ports/wapy-wasm/vmsl/unwrap.c b/ports/wapy-wasm/vmsl/unwrap.c new file mode 100644 index 000000000..6a3b22396 --- /dev/null +++ b/ports/wapy-wasm/vmsl/unwrap.c @@ -0,0 +1,419 @@ +#define VMTRACE (0) +#define DEBUG_BC (0) + + +#if !MICROPY_ENABLE_PYSTACK + #error "need MICROPY_ENABLE_PYSTACK (1)" +#endif + +#if !MICROPY_PERSISTENT_CODE + #pragma message "VM_DECODE_QSTR/VM_DECODE_PTR/VM_DECODE_OBJ" +#endif + + +#if VMTRACE + #define VM_TRACE_QSTR(opc, opv) clog(" %i:%s=%i '%s'", __LINE__, opc, opv, qstr_str(CTX.qst)) + #define VM_TRACE(opc, opv) clog(" %i:%s=%i", __LINE__, opc, opv) + + #define RAISE(o) do { \ + MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(o);\ + clog("%i@%s:exit on ex!", __LINE__, __FILE__);\ + goto exception_handler; } while (0) + + #define RAISE_IF(arg) if (arg) { clog("%i@%s:exit on ex!", __LINE__, __FILE__); goto exception_handler; } + +#else + #define VM_TRACE_QSTR(opc, opv) + #define VM_TRACE(opc, opv) + + #define RAISE(o) do { \ + MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(o);\ + goto exception_handler; } while (0) + + #define RAISE_IF(arg) if (arg) { goto exception_handler; } + +#endif + +#define VM_return(vm_ret_kind) { FRAME_LEAVE();\ +CTX.vm_return_kind = vm_ret_kind;\ +CTX.switch_break_for = 1;\ +break; } + + +#define RAISE_IF_NULL(arg) RAISE_IF(arg == MP_OBJ_NULL) +//#define THE_EXC the_exc +mp_obj_base_t * the_exc ; +//----------------------------------------- + + +#define EX_DECODE_ULABEL size_t ulab = (exip[0] | (exip[1] << 8)); exip += 2 + +#define VM_PUSH_EXC_BLOCK(with_or_finally) do { \ + VM_DECODE_ULABEL; /* except labels are always forward */ \ + ++CTX.exc_sp; \ + CTX.exc_sp->handler = CTX.ip + CTX.ulab; \ + CTX.exc_sp->val_sp = MP_TAGPTR_MAKE(CTX.sp, ((with_or_finally) << 1)); \ + CTX.exc_sp->prev_exc = NULL; \ +} while (0) + +#define VM_POP_EXC_BLOCK() \ + CTX.exc_sp--; /* pop back to previous exception handler */ \ + CLEAR_SYS_EXC_INFO() /* just clear sys.exc_info(), not compliant, but it shouldn't be used in 1st place */ + +#define VM_PUSH(val) (*++CTX.sp = (val)) +#define VM_POP() (*CTX.sp--) +#define VM_TOP() (*CTX.sp) +#define VM_SET_TOP(val) (*CTX.sp = (val)) + +#define VM_DECODE_UINT \ + mp_uint_t unum = 0; \ + do { \ + unum = (unum << 7) + (*CTX.ip & 0x7f); \ + } while ((*CTX.ip++ & 0x80) != 0) + +#define VM_DECODE_ULABEL CTX.ulab = (CTX.ip[0] | (CTX.ip[1] << 8)); CTX.ip += 2 +#define VM_DECODE_SLABEL CTX.slab = (CTX.ip[0] | (CTX.ip[1] << 8)) - 0x8000; CTX.ip += 2 + +#define VM_DECODE_QSTR \ + CTX.qst = CTX.ip[0] | CTX.ip[1] << 8; \ + CTX.ip += 2; + +#define VM_DECODE_PTR \ + VM_DECODE_UINT; \ + CTX.ptr = (void*)(uintptr_t)CTX.code_state->fun_bc->const_table[unum] + +#define VM_DECODE_OBJ \ + VM_DECODE_UINT; \ + mp_obj_t obj = (mp_obj_t)CTX.code_state->fun_bc->const_table[unum] + +#define LOCAL_NAME_ERROR() RAISE(mp_obj_new_exception_msg(&mp_type_NameError, MP_ERROR_TEXT("local variable referenced before assignment"))) + + +#if MICROPY_PY_SYS_EXC_INFO +#define CLEAR_SYS_EXC_INFO() MP_STATE_VM(cur_exception) = NULL; +#else +#define CLEAR_SYS_EXC_INFO() +#endif + + +static mp_obj_t obj_shared; +static int GOTO_OUTER_VM_DISPATCH = 1; + +def_mp_execute_bytecode: { + + // MUST BE SET CTX.inject_exc = MP_OBJ_NULL ; + + #if VM_OPT_COMPUTED_GOTO + #error "no wasm support for VM_OPT_COMPUTED_GOTO" + #else + #define VM_ENTRY(op) case op + #endif +//247 +#if MICROPY_STACKLESS +run_code_state: ; +#endif + +//252 +FRAME_ENTER() +#if MICROPY_STACKLESS +run_code_state_from_return: ; +#endif +FRAME_SETUP(); clog("~vm.c:258 VM(%d)run_code_state", ctx_current); +//257 + // Pointers which are constant for particular invocation of mp_execute_bytecode() + //mp_obj_t * /*const*/ fastn; mp_exc_stack_t * /*const*/ exc_stack; + { + size_t n_state = mp_decode_uint_value(CTX.code_state->fun_bc->bytecode); + CTX.fastn = &CTX.code_state->state[n_state - 1]; + CTX.exc_stack = (mp_exc_stack_t*)(CTX.code_state->state + n_state); + } +//266 + // variables that are visible to the exception handler (declared volatile) + // CTX.exc_sp = MP_TAGPTR_PTR(CTX.code_state->exc_sp); // stack grows up, exc_sp points to top of stack + CTX.exc_sp = MP_CODE_STATE_EXC_SP_IDX_TO_PTR(CTX.exc_stack, CTX.code_state->exc_sp_idx); +VM_DISPATCH_loop: + + if ( CTX.vmloop_state == VM_RESUMING ) { + //? restore what ? + clog("127:unwrap.c VM(%d)restored", ctx_current); + //CTX.ip = CTX.code_state->ip; + //CTX.sp = CTX.code_state->sp; + //obj_shared = CTX.obj_shared ; + CTX.vmloop_state = VM_RUNNING; + } + + if (GOTO_OUTER_VM_DISPATCH) { + clog("134:unwrap.c VM(%d)OUTER_DISPATCH for(){ ip:sp }", ctx_current); // loop to execute byte code + GOTO_OUTER_VM_DISPATCH = 0; + // local variables [that were not visible to the exception handler] + //static const byte *ip; + CTX.ip = CTX.code_state->ip; + //static mp_obj_t *sp; + CTX.sp = CTX.code_state->sp; + } + +#if VMTRACE +if (MP_STATE_THREAD(active_exception) != NULL) { + clog("160:ERROR EXCEPTION ON ENTER[%d]", ctx_current); +} else { + clog("162:unwrap.c VM(%d)DISPATCH->for()", ctx_current); // loop to execute byte code +} +#endif + + for (;;) { + + MICROPY_VM_HOOK_INIT + + // If we have exception to inject, now that we finish setting up + // execution context, raise it. This works as if RAISE_VARARGS + // bytecode was executed. + // Injecting exc into yield from generator is a special case, + // handled by MP_BC_YIELD_FROM itself + if ( (CTX.inject_exc != MP_OBJ_NULL) && (*CTX.ip != MP_BC_YIELD_FROM) ) { +clog("157:unwrap.c pending_exception to inject"); + mp_obj_t exc = CTX.inject_exc; + CTX.inject_exc = MP_OBJ_NULL; + exc = mp_make_raise_obj(exc); + RAISE(exc); + } + + // stores ip pointing to last opcode + CTX.code_state->ip = CTX.ip; +if (trace_on) { + #include "vmsl/vmbc_trace.c" +} else { +#if VMTRACE +if ( (SHOW_OS_LOOP>0) || (source_trace) { + #include "vmsl/vmbc_trace.c" +} +#endif +} + switch (*CTX.ip++) { + // opcode table, can be optimized later with computed goto + // if compiler does not already. + + #include "vmsl/vmswitch.c" + + } // switch + +/* + if return was requested the switch is broken and : + + - return value type is set in CTX.vm_return_kind + + - if (vm_return_kind == MP_VM_RETURN_NORMAL) then result is in CTX.code_state->sp + + - CTX.switch_break_for may be set to break from the for loop from here + +*/ + + if (CTX.switch_break_for) { +#if VMTRACE +clog(" 195:switch_break_for") +#endif + + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { +#if VMTRACE +clog(" 200:switch_break_for/vm_return_kind return value is set") +#endif + RETVAL = *CTX.code_state->sp ; + + // an exception : returned exception is in state[0] + } else if (CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) { +#if VMTRACE +clog(" 208:switch_break_for/vm_return_kind exception value is set") +#endif + RETVAL = CTX.code_state->state[0]; + } else { + clog(" 212:switch_break_for/unrouted vm_return_kind"); + } + break; // go to end of } // for loop + } + +//1 463 +//pending_exception_check: + MICROPY_VM_HOOK_LOOP + + #if MICROPY_ENABLE_SCHEDULER + // This is an inlined variant of mp_handle_pending + if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + if (obj != MP_OBJ_NULL) { + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + if (!mp_sched_num_pending()) { + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + } + MICROPY_END_ATOMIC_SECTION(atomic_state); + RAISE(obj); + } + mp_handle_pending_tail(atomic_state); + } + #else + // This is an inlined variant of mp_handle_pending + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + RAISE(obj); + } + #endif + + #if MICROPY_PY_THREAD_GIL + #if MICROPY_PY_THREAD_GIL_VM_DIVISOR + if (--gil_divisor == 0) + #endif + { + #if MICROPY_PY_THREAD_GIL_VM_DIVISOR + gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; + #endif + #if MICROPY_ENABLE_SCHEDULER + // can only switch threads if the scheduler is unlocked + if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) + #endif + { + MP_THREAD_GIL_EXIT(); + MP_THREAD_GIL_ENTER(); + } + } + #endif + + + continue; + + + exception_handler: + // exception occurred +#if VMTRACE + assert( (MP_STATE_THREAD(active_exception)) != NULL); + clog(" 508:loop[%i] exit on EX!", ctx_current ); +#endif + // clear exception because we caught it + the_exc = (mp_obj_base_t *)MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; + + #if MICROPY_PY_SYS_EXC_INFO + MP_STATE_VM(cur_exception) = the_exc; + #endif + + #if SELECTIVE_EXC_IP + // with selective ip, we store the ip 1 byte past the opcode, so move ptr back + CTX.code_state->ip -= 1; + #endif + +//1 569 + #if MICROPY_PY_SYS_SETTRACE + // Exceptions are traced here + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)the_exc)->type), MP_OBJ_FROM_PTR(&mp_type_Exception))) { + TRACE_TICK(CTX.code_state->ip, CTX.code_state->sp, true /* yes, it's an exception */); + } + #endif + + #if MICROPY_STACKLESS + unwind_loop: + #endif + // Set traceback info (file and line number) where the exception occurred, but not for: + // - constant GeneratorExit object, because it's const + // - exceptions re-raised by END_FINALLY + // - exceptions re-raised explicitly by "raise" + + if ( + (the_exc != &mp_const_GeneratorExit_obj.base) + && (*CTX.code_state->ip != MP_BC_END_FINALLY) + && (*CTX.code_state->ip != MP_BC_RAISE_LAST) + ) { +// same as vmbc_trace + const byte *ipval = CTX.code_state->fun_bc->bytecode; + MP_BC_PRELUDE_SIG_DECODE(ipval); + MP_BC_PRELUDE_SIZE_DECODE(ipval); + const byte *bytecode_start = ipval + n_info + n_cell; + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + bytecode_start = MP_ALIGN(bytecode_start, sizeof(mp_uint_t)); + #endif + size_t bc = CTX.code_state->ip - bytecode_start; + #if MICROPY_PERSISTENT_CODE + qstr block_name = ipval[0] | (ipval[1] << 8); + qstr source_file = ipval[2] | (ipval[3] << 8); + ipval += 4; + #else + qstr block_name = mp_decode_uint_value(ipval); + ipval = mp_decode_uint_skip(ipval); + qstr source_file = mp_decode_uint_value(ipval); + ipval = mp_decode_uint_skip(ipval); + #endif + size_t source_line = mp_bytecode_get_source_line(ipval, bc); +// /vmbc_trace + mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(the_exc), source_file, source_line, block_name); + } +//1 615 + while ((CTX.exc_sp >= CTX.exc_stack) && (CTX.exc_sp->handler <= CTX.code_state->ip)) { + + // nested exception + + assert(CTX.exc_sp >= CTX.exc_stack); + + // TODO make a proper message for nested exception + // at the moment we are just raising the very last exception (the one that caused the nested exception) + + clog("// move up to previous exception handler"); // move up to previous exception handler + VM_POP_EXC_BLOCK(); + } + + if (CTX.exc_sp >= CTX.exc_stack) { + // catch exception and pass to byte code + +//PROBLEM HERE, ip was not set. + CTX.ip = (CTX.code_state->ip = CTX.exc_sp->handler); + mp_obj_t *sptr = MP_TAGPTR_PTR(CTX.exc_sp->val_sp); + // save this exception in the stack so it can be used in a reraise, if needed + CTX.exc_sp->prev_exc = the_exc; + // push exception object so it can be handled by bytecode + VM_PUSH(MP_OBJ_FROM_PTR(the_exc)); + CTX.code_state->sp = sptr; +#if VMTRACE + clog(" 638: where's my EX handler ???"); +#endif + + continue; + +//PROBLEM IS HERE + #if MICROPY_STACKLESS + + } else if (CTX.code_state->prev != NULL) { + mp_globals_set(CTX.code_state->old_globals); + mp_code_state_t *new_code_state = CTX.code_state->prev; + #if MICROPY_ENABLE_PYSTACK + // Free code_state, and args allocated by mp_call_prepare_args_n_kw_var + // (The latter is implicitly freed when using pystack due to its LIFO nature.) + // The sizeof in the following statement does not include the size of the variable + // part of the struct. This arg is anyway not used if pystack is enabled. + mp_nonlocal_free(CTX.code_state, sizeof(mp_code_state_t)); + #endif + CTX.code_state = new_code_state; + size_t n_state = CTX.code_state->n_state; + CTX.fastn = &CTX.code_state->state[n_state - 1]; + CTX.exc_stack = (mp_exc_stack_t*)(CTX.code_state->state + n_state); + // variables that are visible to the exception handler (declared volatile) + CTX.exc_sp = MP_CODE_STATE_EXC_SP_IDX_TO_PTR(CTX.exc_stack, CTX.code_state->exc_sp_idx); // stack grows up, exc_sp points to top of stack + goto unwind_loop; + + #endif + } else { + // propagate exception to higher level + // Note: ip and sp don't have usable values at this point + CTX.code_state->state[0] = MP_OBJ_FROM_PTR(the_exc); // put exception here because sp is invalid + RETVAL = MP_OBJ_FROM_PTR(the_exc); + FRAME_LEAVE(); + CTX.vm_return_kind = MP_VM_RETURN_EXCEPTION; + clog("667: return EX"); + break; + } + + } // for loop + + CTX.switch_break_for = 0 ; + RETURN; + + + +} // def_mp_execute_bytecode diff --git a/ports/wapy-wasm/vmsl/vmbc_call_function.c b/ports/wapy-wasm/vmsl/vmbc_call_function.c new file mode 100644 index 000000000..9af8927c6 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_call_function.c @@ -0,0 +1,151 @@ + + + + + + + + + + + +//1022 + VM_ENTRY(MP_BC_CALL_FUNCTION): { + VM_TRACE("MP_BC_CALL_FUNCTION", MP_BC_CALL_FUNCTION); + + FRAME_UPDATE(); + VM_DECODE_UINT; + + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe); + #if MICROPY_STACKLESS + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*CTX.sp, unum & 0xff, (unum >> 8) & 0xff, CTX.sp + 1); + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; +/* +deep_recursion_error: + mp_raise_recursion_depth(); + RAISE_IF(true); +*/ + #endif //MICROPY_STACKLESS_STRICT + } else + #endif //!MICROPY_ENABLE_PYSTACK + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } else + clog("35:MP_BC_CALL_FUNCTION not mp_type_fun_bc"); + #endif //MICROPY_STACKLESS + //1058 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + const char *fn; + + + + + ctx_get_next(CTX_NEW); + NEXT.self_in = *CTX.sp; + NEXT.sp = CTX.sp; + NEXT.n_args = unum & 0xff; + NEXT.n_kw = (unum >> 8) & 0xff; + NEXT.args = CTX.sp + 1; + + + + if (!*CTX.sp) { + ctx_abort(); // 39 + clog("47:MP_BC_CALL_FUNCTION NPE"); + mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "Invalid Function Name->Pointer mapping called"); + mp_raise_o(obj); + RAISE_IF_NULL(VM_SET_TOP(MP_OBJ_NULL)); + continue; + } + + fn = qstr_str(mp_obj_fun_get_name(NEXT.self_in)); + + if ( strlen(fn)>1 ) { +#if DEBUG_BC + clog(" 99:MP_BC_CALL_FUNCTION [%s %zu '%s']",mp_obj_get_type_str(*CTX.sp), mp_obj_fun_get_name(*CTX.sp), fn); +#endif + GOSUB(def_mp_call_function_n_kw, fn ); + } else { +#if DEBUG_BC + clog(" 102:MP_BC_CALL_FUNCTION [%s %zu]",mp_obj_get_type_str(*CTX.sp), mp_obj_fun_get_name(*CTX.sp)); +#endif + GOSUB(def_mp_call_function_n_kw, mp_obj_get_type_str(NEXT.self_in) ); + } + + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("120:MP_BC_CALL_FUNCTION[%s]/exit on EX!", fn); + } + + RAISE_IF_NULL(VM_SET_TOP(SUBVAL)); + + continue; + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ports/wapy-wasm/vmsl/vmbc_call_function_var_kw.c b/ports/wapy-wasm/vmsl/vmbc_call_function_var_kw.c new file mode 100644 index 000000000..ba8ba61ee --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_call_function_var_kw.c @@ -0,0 +1,68 @@ +//1062 OK+ +VM_ENTRY(MP_BC_CALL_FUNCTION_VAR_KW): { + FRAME_UPDATE(); + VM_DECODE_UINT; + VM_TRACE("MP_BC_CALL_FUNCTION_VAR_KW", MP_BC_CALL_FUNCTION_VAR_KW); + + // unum & 0xff == n_positional + // (unum >> 8) & 0xff == n_keyword + // We have following stack layout here: + // fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 2; + #if MICROPY_STACKLESS + + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + mp_call_args_t out_args; + mp_call_prepare_args_n_kw_var(false, unum, CTX.sp, &out_args); + + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, + out_args.n_args, out_args.n_kw, out_args.args); + #if !MICROPY_ENABLE_PYSTACK + // Freeing args at this point does not follow a LIFO order so only do it if + // pystack is not enabled. For pystack, they are freed when code_state is. + mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); + #endif + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; + #endif + } else + #endif + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } + #endif + + ctx_get_next(CTX_NEW); + + static mp_call_args_t out_args; + mp_call_prepare_args_n_kw_var(false, unum, CTX.sp, &out_args); + + + NEXT.args = out_args.args; + NEXT.alloc = out_args.n_alloc * sizeof(mp_obj_t) ; + + NEXT.self_in = out_args.fun; + NEXT.n_args = out_args.n_args; + NEXT.n_kw = out_args.n_kw; + +// DO NOT USE LINE 57 (GOSUB from BC_CALL_METHOD) + + GOSUB(def_mp_call_function_n_kw, "BC_CALL_FUNCTION_VAR_KW"); + + if (CTX.sub_alloc) + mp_nonlocal_free(CTX.sub_args, CTX.sub_alloc); + + VM_SET_TOP(SUBVAL); + continue; +} diff --git a/ports/wapy-wasm/vmsl/vmbc_call_method.c b/ports/wapy-wasm/vmsl/vmbc_call_method.c new file mode 100644 index 000000000..9f16edd54 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_call_method.c @@ -0,0 +1,83 @@ +// OK+ + +VM_ENTRY(MP_BC_CALL_METHOD): { + VM_DECODE_UINT; + // unum & 0xff == n_positional + // (unum >> 8) & 0xff == n_keyword + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1; + + #if MICROPY_STACKLESS + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + size_t n_args = unum & 0xff; + size_t n_kw = (unum >> 8) & 0xff; + int adjust = (CTX.sp[1] == MP_OBJ_NULL) ? 0 : 1; + + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*CTX.sp, n_args + adjust, n_kw, CTX.sp + 2 - adjust); + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; + #endif + } else + #endif + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } else { + clog("38:MP_BC_CALL_METHOD native"); + native = 1; + } + + #endif + + ctx_get_next(CTX_NEW); + NEXT.args = CTX.sp ; + + NEXT.self_in = NEXT.args[0]; + NEXT.n_args = unum & 0xff; + NEXT.n_kw = (unum >> 8) & 0xff; + + { + int adjust = (NEXT.args[1] == MP_OBJ_NULL) ? 0 : 1; + NEXT.n_args += adjust; + NEXT.args += 2 - adjust; + } + + + + +//57: DO NOT USE LINE 57 FOR SUB + + + + + + + + + + + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + qstr FUN_QSTR = mp_obj_fun_get_name( NEXT.self_in ); + if (FUN_QSTR != MP_QSTR_ ) { + GOSUB(def_mp_call_function_n_kw, qstr_str(FUN_QSTR) ); + } else { + GOSUB(def_mp_call_function_n_kw, "BC_CALL_METHOD(?name?)" ); + } + } else { + GOSUB(def_mp_call_function_n_kw, "BC_CALL_METHOD(native)" ); + } + + VM_SET_TOP(SUBVAL); + continue; +} + + diff --git a/ports/wapy-wasm/vmsl/vmbc_for_iter.c b/ports/wapy-wasm/vmsl/vmbc_for_iter.c new file mode 100644 index 000000000..ef0aaee1e --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_for_iter.c @@ -0,0 +1,86 @@ +VM_ENTRY(MP_BC_FOR_ITER): { + FRAME_UPDATE(); + VM_DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward + + CTX.code_state->sp = CTX.sp; + + ctx_get_next(CTX_COPY); + NEXT.return_value = MP_OBJ_NULL;; + NEXT.send_value = mp_const_none; + NEXT.throw_value = MP_OBJ_NULL; + + if (CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] == MP_OBJ_NULL) { + NEXT.self_in = CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 2]; + } else { + NEXT.self_in = MP_OBJ_FROM_PTR(&CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 1]); + } +// ============= mp_obj_t value = mp_iternext_allow_raise(obj); =============== +// unwrap from runtime_no_nlr.c:1259 + const mp_obj_type_t *type = mp_obj_get_type(NEXT.self_in); + + if (type->iternext != NULL) { + + clog(">>> BC_FOR_ITER:gen_instance_iternext\n" ); + RETVAL = type->iternext(NEXT.self_in); + + if ( (void*)*type->iternext == &gen_instance_iternext ) { + clog(">>> BC_FOR_ITER:type->iternext\n" ); +#if 0 + static mp_vm_return_kind_t mpsl_obj_gen_resume; + + #include "vm/mpsl_obj_gen_resume.c" + + switch (mpsl_obj_gen_resume) { + case MP_VM_RETURN_NORMAL: + default: + // Optimize return w/o value in case generator is used in for loop + if (return_value == mp_const_none || return_value == MP_OBJ_STOP_ITERATION) { + return_value = MP_OBJ_STOP_ITERATION; + break; + } else { + nlr_raise(mp_obj_new_exception_args(&mp_type_StopIteration, 1, &return_value)); + break; + } + + case MP_VM_RETURN_YIELD: + //return_value = return_value; + break; + + case MP_VM_RETURN_EXCEPTION: + nlr_raise(return_value); + } + + +#endif + clog("<<< BC_FOR_ITER:type->iternext_return\n"); + } + + clog("<<< BC_FOR_ITER:gen_instance_iternext_return\n" ); + + } else { + // check for __next__ method + mp_obj_t dest[2]; + mp_load_method_maybe(NEXT.self_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __next__ exists, call it and return its result +clog(">>> BC_FOR_ITER:mp_call_method_n_kw\n"); + RETVAL = mp_call_method_n_kw(0, 0, dest); +clog("<<< BC_FOR_ITER:mp_call_method_n_kw_return\n"); + } else { + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%s' object isn't an iterator", mp_obj_get_type_str(NEXT.self_in))); + } + } +// runtime_no_nlr.c:1280 +//========================== end mp_iternext_allow_raise(mp_obj_t o_in) ================== + + if (RETVAL == MP_OBJ_STOP_ITERATION) { + CTX.sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator + CTX.ip += CTX.ulab; // jump to after for-block + } else { + VM_PUSH(RETVAL); // push the next iteration value + } + ctx_abort(); + continue; +} + diff --git a/ports/wapy-wasm/vmsl/vmbc_import.c b/ports/wapy-wasm/vmsl/vmbc_import.c new file mode 100644 index 000000000..aa3ab1214 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_import.c @@ -0,0 +1,102 @@ +// OK+ + +#ifndef MICROPY_CAN_OVERRIDE_BUILTINS +#define MICROPY_CAN_OVERRIDE_BUILTINS 1 +#endif + +//1364 +VM_ENTRY(MP_BC_IMPORT_NAME): { + FRAME_UPDATE(); + VM_DECODE_QSTR; // qst => import [name] + +#if 1 //DEBUG_BC + cdbg("13:BC_IMPORT_NAME(%d:%d): [%s]\n", ctx_current, CTX.sub_id, qstr_str(CTX.qst) ); +#endif + + ctx_get_next(CTX_COPY); + //NEXT.qst = CTX.qst; + NEXT.n_args = 5 ; + NEXT.n_kw = 0; + NEXT.argv[0] = MP_OBJ_NEW_QSTR(CTX.qst) ; + NEXT.argv[1] = mp_const_none; // TODO should be globals + NEXT.argv[2] = mp_const_none; // TODO should be globals + NEXT.argv[3] = VM_POP(); // fromlist + NEXT.argv[4] = VM_TOP(); + + NEXT.args = &NEXT.argv[0] ; //implicit + + //if ( !strcmp(qstr_str(CTX.qst),"syscall") ) + // clog(" BC_IMPORT_NAME(%d:%d) import %s->pause", ctx_current, CTX.sub_id, qstr_str(CTX.qst) ); + + + #if MICROPY_CAN_OVERRIDE_BUILTINS + { + mp_obj_dict_t *bo_dict = MP_STATE_VM(mp_module_builtins_override_dict); + // lookup __import__ and call that instead of going straight to builtin implementation + if (bo_dict != NULL) { + mp_map_elem_t *cust_imp = mp_map_lookup(&bo_dict->map, MP_OBJ_NEW_QSTR(MP_QSTR___import__), MP_MAP_LOOKUP); + if (cust_imp != NULL) { + NEXT.self_in = cust_imp->value ; + GOSUB(def_mp_call_function_n_kw, qstr_str(CTX.qst)); + + goto RET_import_name; + } // no continuation + } + } + #endif + + SUBVAL = mp_builtin___import__(5, NEXT.argv); + + //ctx_release(); + + RET_import_name: + // not a sub, here we don't return + RETVAL = SUBVAL ; //VM_SET_TOP( CTX.sub_value ); + VM_SET_TOP( RETVAL ); + + RAISE_IF_NULL( RETVAL ); + // TODO:CTX + + if ( !strcmp(qstr_str(CTX.qst),"aio_suspend") ) { + //clog(" BC_IMPORT_NAME(%d:%d) import %s->pause", ctx_current, CTX.sub_id, qstr_str(CTX.qst) ); + BRANCH(VM_syscall, VMOP_PAUSE, VM_DISPATCH_loop, "BC_IMPORT_NAME(aio_suspend)"); + } + + continue; +} + +//1374 +VM_ENTRY(MP_BC_IMPORT_FROM): { + FRAME_UPDATE(); + VM_DECODE_QSTR; + mp_obj_t obj = mp_import_from(VM_TOP(), CTX.qst); + RAISE_IF_NULL(obj); + VM_PUSH(obj); + continue; +} + +//1386 +VM_ENTRY(MP_BC_IMPORT_STAR): { + mp_import_all(VM_POP()); + continue; +} + + + + + + + + + + + + + + + + + + + +// diff --git a/ports/wapy-wasm/vmsl/vmbc_raise.c b/ports/wapy-wasm/vmsl/vmbc_raise.c new file mode 100644 index 000000000..ce2ad46f4 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_raise.c @@ -0,0 +1,28 @@ + +//1266 + VM_ENTRY(MP_BC_RAISE_LAST): { + // search for the inner-most previous exception, to reraise it + mp_obj_t obj = MP_OBJ_NULL; + for (mp_exc_stack_t *e = CTX.exc_sp; e >= CTX.exc_stack; --e) { + if (e->prev_exc != NULL) { + obj = MP_OBJ_FROM_PTR(e->prev_exc); + break; + } + } + if (obj == MP_OBJ_NULL) { + obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "no active exception to reraise"); + } + RAISE(obj); + } +//1282 + VM_ENTRY(MP_BC_RAISE_OBJ): { + mp_obj_t obj = mp_make_raise_obj(VM_TOP()); + RAISE(obj); + } +//1288 + VM_ENTRY(MP_BC_RAISE_FROM): { + mp_warning(NULL, "exception chaining not supported"); + CTX.sp--; // ignore (pop) "from" argument + mp_obj_t obj = mp_make_raise_obj(VM_TOP()); + RAISE(obj); + } diff --git a/ports/wapy-wasm/vmsl/vmbc_trace.c b/ports/wapy-wasm/vmsl/vmbc_trace.c new file mode 100644 index 000000000..c0c783978 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbc_trace.c @@ -0,0 +1,39 @@ + const byte *ipval = CTX.code_state->fun_bc->bytecode; + MP_BC_PRELUDE_SIG_DECODE(ipval); + MP_BC_PRELUDE_SIZE_DECODE(ipval); + const byte *bytecode_start = ipval + n_info + n_cell; + + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + bytecode_start = MP_ALIGN(bytecode_start, sizeof(mp_uint_t)); + #endif + + size_t bc = CTX.code_state->ip - bytecode_start; + #if MICROPY_PERSISTENT_CODE + qstr source_file = ipval[2] | (ipval[3] << 8); + ipval += 4; + #else + ipval = mp_decode_uint_skip(ipval); + qstr source_file = mp_decode_uint_value(ipval); + ipval = mp_decode_uint_skip(ipval); + #endif + size_t source_line = mp_bytecode_get_source_line(ipval, bc); + + static size_t last_line = 0; + + if (source_line!=last_line) { + if (source_line) { + trace_prev_line = trace_file; + trace_prev_line = trace_line; + trace_file = source_file; + trace_line = source_line; + } +#if TRACE_ON + cdbg("\nbc:%i ctx=%i %s:%zu", *CTX.ip , ctx_current,qstr_str(source_file), source_line); +#else + clog("\nbc:%i ctx=%i %s:%zu", *CTX.ip , ctx_current,qstr_str(source_file), source_line); +#endif + + last_line = source_line; + } + diff --git a/ports/wapy-wasm/vmsl/vmbclookup.c b/ports/wapy-wasm/vmsl/vmbclookup.c new file mode 100644 index 000000000..dce31ca11 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmbclookup.c @@ -0,0 +1,78 @@ +//385 + VM_ENTRY(MP_BC_LOAD_NAME): { + VM_DECODE_QSTR; + mp_map_elem_t *elem = mp_map_cached_lookup(&mp_locals_get()->map, CTX.qst, (uint8_t*)CTX.ip); + + mp_obj_t obj; + if (elem != NULL) { + obj = elem->value; + } else { + obj = mp_load_name(CTX.qst); + RAISE_IF_NULL(obj); + } + VM_PUSH(obj); + CTX.ip++; + continue; + } + +//413 + VM_ENTRY(MP_BC_LOAD_GLOBAL): { + VM_DECODE_QSTR; + mp_map_elem_t *elem = mp_map_cached_lookup(&mp_globals_get()->map, CTX.qst, (uint8_t*)CTX.ip); + mp_obj_t obj; + if (elem != NULL) { + obj = elem->value; + } else { + obj = mp_load_global(CTX.qst); + RAISE_IF_NULL(obj); + } + VM_PUSH(obj); + CTX.ip++; + continue; + } + +//442 + VM_ENTRY(MP_BC_LOAD_ATTR): { + FRAME_UPDATE(); + VM_DECODE_QSTR; + mp_obj_t top = VM_TOP(); + mp_map_elem_t *elem = NULL; + if (mp_obj_is_instance_type(mp_obj_get_type(top))) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(top); + elem = mp_map_cached_lookup(&self->members, CTX.qst, (uint8_t*)CTX.ip); + } + mp_obj_t obj; + if (elem != NULL) { + obj = elem->value; + } else { + obj = mp_load_attr(top, CTX.qst); + RAISE_IF_NULL(obj); + } + VM_SET_TOP(obj); + CTX.ip++; + continue; + } + + // This caching code works with MICROPY_PY_BUILTINS_PROPERTY and/or + // MICROPY_PY_DESCRIPTORS enabled because if the attr exists in + // self->members then it can't be a property or have descriptors. A + // consequence of this is that we can't use MP_MAP_LOOKUP_ADD_IF_NOT_FOUND + // in the fast-path below, because that store could override a property. + VM_ENTRY(MP_BC_STORE_ATTR): { + FRAME_UPDATE(); + VM_DECODE_QSTR; + mp_map_elem_t *elem = NULL; + mp_obj_t top = VM_TOP(); + if (mp_obj_is_instance_type(mp_obj_get_type(top)) && CTX.sp[-1] != MP_OBJ_NULL) { + mp_obj_instance_t *self = MP_OBJ_TO_PTR(top); + elem = mp_map_cached_lookup(&self->members, CTX.qst, (uint8_t*)CTX.ip); + } + if (elem != NULL) { + elem->value = CTX.sp[-1]; + } else { + RAISE_IF_NULL(mp_store_attr(CTX.sp[0], CTX.qst, CTX.sp[-1])); + } + CTX.sp -= 2; + CTX.ip++; + continue; + } diff --git a/ports/wapy-wasm/vmsl/vmconf.h b/ports/wapy-wasm/vmsl/vmconf.h new file mode 100644 index 000000000..c2e2bf30d --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmconf.h @@ -0,0 +1,32 @@ +//TODO sys max recursion handling. +#define SYS_MAX_RECURSION 128 +#define MAX_BRANCHING 128 + +//order matters +#define VM_IDLE 0 +#define VM_WARMUP 1 +#define VM_RUNNING 2 +#define VM_RESUMING 3 +#define VM_AIO 4 +#define VM_PAUSED 5 +#define VM_SYSCALL 6 +#define VM_HCF 7 + +static int VMOP = -1; + +#define VMOP_NONE 0 +#define VMOP_INIT 1 +#define VMOP_WARMUP 2 +#define VMOP_CALL 3 +#define VMOP_CRASH 4 +#define VMOP_AIO 5 +#define VMOP_PAUSE 6 +#define VMOP_SYSCALL 7 + +static int sub_tracking = 0; + +static int ctx_current = 1; +static int ctx_next = 2; + + + diff --git a/ports/wapy-wasm/vmsl/vmreg.c b/ports/wapy-wasm/vmsl/vmreg.c new file mode 100644 index 000000000..b52d4d033 --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmreg.c @@ -0,0 +1,327 @@ +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#define STRINGIFY2(a, b) a ## b +#define CONCAT(x, y) STRINGIFY2(x, y) + + + +#ifndef VM_REG_H + //TODO sys max recursion handling. + #define SYS_MAX_RECURSION 32 + #define MAX_BRANCHING 128 + + //order matters + #define VM_IDLE 0 + #define VM_WARMUP 1 + #define VM_RUNNING 2 + #define VM_RESUMING 3 + #define VM_PAUSED 4 + #define VM_SYSCALL 5 + #define VM_HCF 6 + + static int VMOP = -1; + + #define VMOP_NONE 0 + #define VMOP_INIT 1 + #define VMOP_WARMUP 2 + #define VMOP_CALL 3 + #define VMOP_CRASH 4 + #define VMOP_PAUSE 5 + #define VMOP_SYSCALL 6 + + static int sub_tracking = 0; + + static int ctx_current = 1; + static int ctx_next = 2; + + + + #define clog(...) { fprintf(stderr, __VA_ARGS__ );fprintf(stderr, "\n"); } + + struct mp_registers { + int sub_id ; + //who created us + int parent ; + //who did we create and was running last + int childcare ; + + // execution path + int pointer; + int switch_break_for; + int vmloop_state; + }; + + static struct mp_registers mpi_ctx[SYS_MAX_RECURSION]; + + void mp_new_interpreter(void * mpi, int ctx, int parent, int childcare) { + + mpi_ctx[ctx].parent = parent; + mpi_ctx[ctx].childcare = childcare; + // + mpi_ctx[ctx].pointer = 0; + mpi_ctx[ctx].sub_id = sub_tracking++; + mpi_ctx[ctx].vmloop_state = VM_IDLE; + + } +#endif + + +#define JMP_NONE (void*)0 +#define TYPE_JUMP 0 +#define TYPE_SUB 1 + +#define JUMPED_IN reached_point[CTX.pointer] +#define JUMP_TYPE type_point[CTX.pointer] +#define ENTRY_POINT entry_point[CTX.pointer] +#define EXIT_POINT exit_point[CTX.pointer] + +static int point_ptr = 0; // index 0 is reserved for no branching + +static int come_from[MAX_BRANCHING]; +static int type_point[MAX_BRANCHING]; +static int reached_point[MAX_BRANCHING]; +static void* entry_point[MAX_BRANCHING]; +static void* exit_point[MAX_BRANCHING]; + + + +static struct mp_registers mpi_ctx[SYS_MAX_RECURSION]; + +#define CTX mpi_ctx[ctx_current] +#define NEXT mpi_ctx[ctx_next] +#define PARENT mpi_ctx[CTX.parent] + +#define CTX_STATE CTX.vmloop_state +#define NEXT_STATE NEXT.vmloop_state + + +static void* crash_point = JMP_NONE; + +void *crash(const char *panic){ + clog("%s", panic); + VMOP = VMOP_CRASH; + return crash_point; + +} + +#define FATAL(msg) goto *crash(msg) + +#if VM_FULL + #define SUBVAL CTX.sub_value + #define RETVAL CTX.return_value +#endif + +#define RETURN goto *ctx_return() +#define COME_FROM goto *ctx_come_from() + + + +void ctx_free(){ + clog("CTX %d freed(free) for %d", ctx_current, CTX.parent); + CTX_STATE = VM_IDLE; +} + +void ctx_abort(){ + clog("CTX %d freed(abort) for %d", ctx_next, ctx_current); + NEXT_STATE = VM_IDLE; +} + + +static int come_from[MAX_BRANCHING]; + +#define deferred 1 + +void zigzag(void* jump_entry, void* jump_back, int jump_type){ + come_from[++point_ptr] = CTX.pointer; // when set to 0 its origin mark. + CTX.pointer = point_ptr; + JUMPED_IN = 0; + ENTRY_POINT = jump_entry; + EXIT_POINT = jump_back; + JUMP_TYPE = jump_type; +} + + + + +#define CTX_COPY 1 +#define CTX_NEW 0 + +// prepare a child context for a possibly recursive call, +// NEXT.* will then gives access to that ctx (inited from CTX if CTX_COPY ) until GOSUB() +// after GOSUB CTX is restored automaticallly to parent and return value is in CTX.sub_value + +#if VM_FULL +void ctx_get_next(int copy) { + //push + int ctx; + for (ctx=3; ctx 2) && (CTX.code_state == NULL)) + clog(" ======== no code_state for slot %i->%i ============", ctx_current, ctx_next); + + NEXT.self_in = CTX.self_in; + NEXT.self_fun = CTX.self_fun; + NEXT.ip = CTX.ip; + NEXT.sp = CTX.sp; + NEXT.exc_stack = CTX.exc_stack; + NEXT.n_args = CTX.n_args; + NEXT.n_kw = CTX.n_kw; + NEXT.args = CTX.args; +//? + NEXT.n_state = CTX.n_state; + NEXT.state_size = CTX.state_size; + NEXT.inject_exc = CTX.inject_exc; + } +} +#endif + + +void ctx_switch(){ + NEXT_STATE = VM_RUNNING; + ctx_current = ctx_next; + #if CTX_DEBUG + clog("CTX %d locked for %d",ctx_current,CTX.parent); + #endif +} + +void* ctx_branch(void* jump_entry,int vmop, void *jump_back, const char *jto, const char *jback, const char* context, int defer) { + VMOP = vmop; + zigzag(jump_entry, jump_back, TYPE_JUMP); + clog(" ZZ > %s(...) %s -> %s @%d",context, jto, jback, ctx_current); + if (!defer) + JUMPED_IN = 1 ; + return jump_entry; +} + +#define BRANCH(arg0, vmop, arg1, arg2) goto *ctx_branch(&&arg0, vmop, &&arg1, TOSTRING(arg0), TOSTRING(arg1), arg2, !deferred) + +#define SYSCALL(arg0, vmop, arg1, arg2) \ +{\ + ctx_branch(&&arg0, vmop, &&arg1, TOSTRING(arg0), TOSTRING(arg1), arg2, deferred);\ + goto VM_syscall;\ +} + + +void* ctx_call(void* jump_entry, void *jump_back, const char *jt_origin,const char *context, int defer) { + VMOP = VMOP_CALL; + zigzag(jump_entry, jump_back, TYPE_JUMP); + clog(" CC > %s->%s(...) @%d", context, jt_origin, ctx_current); + if (!defer) + JUMPED_IN = 1 ; + return jump_entry; +} + +#define CALL(arg0, caller) \ +{\ + ctx_call(&&arg0, &&CONCAT(call_,__LINE__), TOSTRING(arg0), caller, deferred);\ + goto VM_syscall;\ + CONCAT(call_,__LINE__):;\ +} + +#define JUMP(arg0, caller) \ +{\ + goto *ctx_call(&&arg0, &&CONCAT(call_,__LINE__), TOSTRING(arg0), caller, !deferred);\ + CONCAT(call_,__LINE__):;\ +} + +void* ctx_sub(void* jump_entry, void* jump_back, const char* jto, const char* jback, const char* context) { + // set the new context so zigzag @ are set + ctx_switch(); + + zigzag(jump_entry, jump_back, TYPE_SUB); + clog(" Begin[%d:%d] %s(...) %s -> %s %d->%d", ctx_current, CTX.sub_id, context, jto, jback, CTX.parent, ctx_current); + JUMPED_IN = 1 ; + return jump_entry; +} + +//#define GOSUB(arg0, arg1, arg2) goto *ctx_sub(&&arg0, &&arg1, TOSTRING(arg0), TOSTRING(arg1), arg2 ) + +#define GOSUB(arg0, caller) \ +{\ + goto *ctx_sub(&&arg0, &&CONCAT(call_,__LINE__), TOSTRING(arg0), TOSTRING(CONCAT(call_,__LINE__)), caller ) ;\ + CONCAT(call_,__LINE__):;\ +} + + +void* ctx_come_from() { + // just going back from branching + int ptr_back = come_from[CTX.pointer]; + + void* return_point; + + if (JUMP_TYPE!=TYPE_JUMP) + return crash("BRANCH EXIT in SUB CONTEXT"); + + return_point = EXIT_POINT; + + if (point_ptr!=CTX.pointer) { + return crash("ERROR: jumping back from upper branch not allowed, maybe ctx_get_next missing for GOSUB ?"); + } else + point_ptr--; + clog("<<< ZZ[%d]",ctx_current); + // go up one level of branching ( or if 0 reach root ) + CTX.pointer = ptr_back; + return return_point; +} + + + +void* ctx_return(){ + void* return_point; + int ptr_back = come_from[CTX.pointer]; + +#if VM_FULL + PARENT.sub_vm_return_kind = CTX.vm_return_kind; + PARENT.sub_value = CTX.return_value; + PARENT.sub_alloc = CTX.alloc; + PARENT.sub_args = CTX.args ; +#endif + + // possibly return in upper branch ? + if (point_ptr!=CTX.pointer) { + clog("ERROR not on leaf branch"); + emscripten_cancel_main_loop(); + } else { + point_ptr -= 1; + } + return_point = exit_point[CTX.pointer]; + + + // not a zigzag branching free registers + if (!ptr_back) + ctx_free(); + + // not a zigzag go upper context + if ( JUMP_TYPE == TYPE_SUB) + ctx_current = CTX.parent; + else + return crash("239:ERROR: RETURN in non SUB BRANCH"); + + return return_point; +} + + + + + + diff --git a/ports/wapy-wasm/vmsl/vmreg.h b/ports/wapy-wasm/vmsl/vmreg.h new file mode 100644 index 000000000..6e86d8c3f --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmreg.h @@ -0,0 +1,217 @@ +#include "py/obj.h" +#include "py/runtime.h" + +//#include "lib/bipbuffer/bipbuffer.h" +//#include "lib/bipbuffer/bipbuffer.c" + + +typedef struct _mp_obj_closure_t { + mp_obj_base_t base; + mp_obj_t fun; + size_t n_closed; + mp_obj_t closed[]; +} mp_obj_closure_t; + + +typedef struct _mp_obj_gen_instance_t { + mp_obj_base_t base; + mp_obj_dict_t *globals; + mp_code_state_t code_state; +} mp_obj_gen_instance_t; + +#include "vmsl/vmconf.h" + +struct mp_registers { + // builtins override dict ? + // __main__ ? + // sys.(std*) ? + // sys.argv + + // cpu time load stats ? + + //who created us + int parent ; + + // + int sub_id ; + + //who did we create and was running last + int childcare ; + + int vmloop_state; + nlr_buf_t nlr; + + mp_lexer_t *lex; + mp_reader_t reader; + + qstr source_name; + mp_parse_tree_t parse_tree; + + mp_obj_t * /*const*/ fastn; + mp_exc_stack_t * /*const*/ exc_stack; + + size_t n_state; + size_t state_size; + +//bytecode switch case + + volatile mp_obj_t inject_exc; + mp_code_state_t *code_state; + mp_exc_stack_t *volatile exc_sp; + + +// execution path + int pointer; + int switch_break_for; + + +// parent return state + size_t ulab; + size_t slab; + mp_obj_t *sp; + const byte *ip; + mp_obj_t obj_shared; + void *ptr; + +// mp_import state + qstr qst; + mp_obj_t argv[5]; + + +// mp_call_function_n_kw, closure_call and iterators state + mp_obj_t self_in; + mp_obj_fun_builtin_var_t *self_fb; + mp_obj_fun_bc_t *self_fun; + mp_obj_closure_t *self_clo; + + int alloc; + + //mp_obj_type_t *type; + + size_t n_args; + size_t n_kw; + //const + mp_obj_t *args; + + mp_obj_gen_instance_t* generator; + mp_obj_t send_value, throw_value, return_value; + + mp_obj_t sub_value; // child ctx computed value. + mp_obj_t *sub_args ; // child allocated memory ptr + int sub_alloc ; // child allocated memory size + mp_vm_return_kind_t sub_vm_return_kind; // child result on mp_execute_bytecode() calls ( can be recursive ) + + mp_vm_return_kind_t vm_return_kind; + // last + //mp_obj_gen_instance_t self_gen; +}; + +static struct mp_registers mpi_ctx[SYS_MAX_RECURSION]; + +void +mp_new_interpreter(void * mpi, int ctx, int parent, int childcare) { + mpi_ctx[ctx].vmloop_state = VM_IDLE; + mpi_ctx[ctx].parent = parent; + mpi_ctx[ctx].childcare = childcare; + mpi_ctx[ctx].pointer = 0; + mpi_ctx[ctx].code_state = NULL ; + //? + mpi_ctx[ctx].state_size = 0; + mpi_ctx[ctx].n_state = 0; + //? + mpi_ctx[ctx].n_args = 0; + mpi_ctx[ctx].n_kw = 0; + mpi_ctx[ctx].args = &mpi_ctx[ctx].argv[0]; + mpi_ctx[ctx].alloc = 0; + mpi_ctx[ctx].switch_break_for = 0; + mpi_ctx[ctx].sub_id = sub_tracking++; + +} + +#define VM_FULL 1 + + +struct mp_stack { + int gen_i; + int gen_n; + int has_default; + int default_value; + int __line__ ; + const char *name; + const char *value; // some in memory rpc struct +}; + + +// Duff's device thanks a lot to https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + + +#define Begin switch(generator_context->__line__) { case 0: + +#define yield(x)\ +do {\ + generator_context->__line__ = __LINE__;\ + return x; \ +case __LINE__:; } while (0) + +#define End }; + + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +// static int enumerator=0; +#define vars(generator_name) static struct mp_stack generator_context = { .gen_i=0, .gen_n = 1, .has_default = 0, .default_value = 666, .__line__ = 0, .name=TOSTRING(generator_name) } + +#define async_def(generator_name, gen_type, ...) \ + gen_type generator_name(struct mp_stack *generator_context) { \ + Begin; \ + __VA_ARGS__ \ + End;\ + generator_context->gen_n = 0;\ + return generator_context->default_value;\ + }\ + +#define async_iter(generator_name) \ + vars(generator_name);\ + if (generator_context.gen_n) generator_context.gen_i=generator_name(&generator_context); \ + if (generator_context.gen_n) + +#define async_next(generator_name, defval) \ + vars(generator_name); if (!generator_context.has_default) { generator_context.has_default = 1; generator_context.default_value = defval; }\ + int had_next = generator_context.gen_n; \ + if (had_next) generator_context.gen_i = generator_name(&generator_context); \ + if (had_next) + + +// -------------------------- attempt to be pythonic -------------------- +#define self (*generator_context) +#define iterator generator_context->gen_i +#define next generator_context->gen_n + + +async_def(gen1, int, { + while (next) { + printf("gen1 %s ",self.name); + if (iterator++ == 10) break; + yield(iterator); + } +}); + + +async_def(gen2, int, { + for (iterator = 0; iterator < 5; iterator++) { + printf("gen2 %s ",self.name); + yield(iterator); + } +}); + +#undef self +#undef iterator +#undef next + +#define iterator generator_context.gen_i + +// ------------------------------------------------------------------------ + + +#define VM_REG_H 1 diff --git a/ports/wapy-wasm/vmsl/vmswitch.c b/ports/wapy-wasm/vmsl/vmswitch.c new file mode 100644 index 000000000..86c2cc2ce --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmswitch.c @@ -0,0 +1,979 @@ +//311 + + VM_ENTRY(MP_BC_LOAD_CONST_FALSE): { + VM_PUSH(mp_const_false); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_NONE): { + VM_PUSH(mp_const_none); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_TRUE): { + VM_PUSH(mp_const_true); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_SMALL_INT): { + mp_int_t num = 0; + if ((CTX.ip[0] & 0x40) != 0) { + // Number is negative + num--; + } + do { num = (num << 7) | (*CTX.ip & 0x7f); } while ((*CTX.ip++ & 0x80) != 0); + VM_PUSH(MP_OBJ_NEW_SMALL_INT(num)); + continue; + } +//339 + VM_ENTRY(MP_BC_LOAD_CONST_STRING): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_CONST_STRING", MP_BC_LOAD_CONST_STRING); + + VM_PUSH(MP_OBJ_NEW_QSTR(CTX.qst)); + continue; + } + + VM_ENTRY(MP_BC_LOAD_CONST_OBJ): { + VM_DECODE_OBJ; + VM_TRACE("MP_BC_LOAD_CONST_OBJ", MP_BC_LOAD_CONST_OBJ); + + VM_PUSH(obj); + continue; + } + + VM_ENTRY(MP_BC_LOAD_NULL): { + VM_PUSH(MP_OBJ_NULL); + continue; + } + + VM_ENTRY(MP_BC_LOAD_FAST_N): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_LOAD_FAST_N", MP_BC_LOAD_FAST_N); + + obj_shared = CTX.fastn[-unum]; + if (obj_shared == MP_OBJ_NULL) + LOCAL_NAME_ERROR(); + // no continuation + + VM_PUSH(obj_shared); + continue; + } + + VM_ENTRY(MP_BC_LOAD_DEREF): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_LOAD_DEREF", MP_BC_LOAD_DEREF); + + obj_shared = mp_obj_cell_get(CTX.fastn[-unum]); + if (obj_shared == MP_OBJ_NULL) + LOCAL_NAME_ERROR(); + // no continuation + + VM_PUSH(obj_shared); + continue; + } + + #if !MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE +//378 + VM_ENTRY(MP_BC_LOAD_NAME): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_NAME", MP_BC_LOAD_NAME); + VM_PUSH(mp_load_name(CTX.qst)); + RAISE_IF_NULL(VM_TOP()); + continue; //? DISPATCH() ? + } +//406 + VM_ENTRY(MP_BC_LOAD_GLOBAL): { + VM_DECODE_QSTR; +if (MP_STATE_THREAD(active_exception) != NULL) clog("EXEXEXEXEXEXEXEXEXEXEXEXEXEXEXEXE 1"); + VM_TRACE_QSTR("MP_BC_LOAD_GLOBAL", MP_BC_LOAD_GLOBAL); + VM_PUSH(mp_load_global(CTX.qst)); +if (MP_STATE_THREAD(active_exception) != NULL) clog("EXEXEXEXEXEXEXEXEXEXEXEXEXEXEXEXE 2"); + RAISE_IF_NULL(VM_TOP()); + + continue; //? DISPATCH() ? + } +//433 + VM_ENTRY(MP_BC_LOAD_ATTR): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_ATTR", MP_BC_LOAD_ATTR); + + VM_SET_TOP(mp_load_attr(VM_TOP(), CTX.qst)); + RAISE_IF_NULL(VM_TOP()); + continue; //? DISPATCH() ? + } +//522 + VM_ENTRY(MP_BC_STORE_ATTR): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_STORE_ATTR",MP_BC_STORE_ATTR); + + RAISE_IF_NULL( mp_store_attr(CTX.sp[0], CTX.qst, CTX.sp[-1]) ); + CTX.sp -= 2; + continue; + } + + #else + #include "vmsl/vmbclookup.c" + #endif + +//467 + VM_ENTRY(MP_BC_LOAD_METHOD): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_METHOD", MP_BC_LOAD_METHOD); + + RAISE_IF_NULL( mp_load_method(*CTX.sp, CTX.qst, CTX.sp) ); + CTX.sp += 1; + continue; + } + + VM_ENTRY(MP_BC_LOAD_SUPER_METHOD): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_LOAD_SUPER_METHOD", MP_BC_LOAD_SUPER_METHOD); + CTX.sp -= 1; + RAISE_IF_NULL( mp_load_super_method(CTX.qst, CTX.sp - 1) ); + continue; + } + + VM_ENTRY(MP_BC_LOAD_BUILD_CLASS): + VM_PUSH(mp_load_build_class()); + continue; + + VM_ENTRY(MP_BC_LOAD_SUBSCR): { + VM_TRACE("MP_BC_LOAD_SUBSCR", MP_BC_LOAD_SUBSCR); + mp_obj_t index = VM_POP(); + VM_SET_TOP(mp_obj_subscr(VM_TOP(), index, MP_OBJ_SENTINEL)); + RAISE_IF_NULL(VM_TOP()); + continue; + } + + VM_ENTRY(MP_BC_STORE_FAST_N): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_STORE_FAST_N", MP_BC_STORE_FAST_N); + CTX.fastn[-unum] = VM_POP(); + continue; + } +//502 + VM_ENTRY(MP_BC_STORE_DEREF): { + VM_DECODE_UINT; + mp_obj_cell_set(CTX.fastn[-unum], VM_POP()); + continue; + } + + VM_ENTRY(MP_BC_STORE_NAME): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_STORE_NAME", MP_BC_STORE_NAME); + mp_store_name(CTX.qst, VM_POP()); + continue; + } + + VM_ENTRY(MP_BC_STORE_GLOBAL): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_STORE_GLOBAL", MP_BC_STORE_GLOBAL); + mp_store_global(CTX.qst, VM_POP()); + continue; + } + + +//558 + VM_ENTRY(MP_BC_STORE_SUBSCR): { + RAISE_IF_NULL( mp_obj_subscr(CTX.sp[-1], CTX.sp[0], CTX.sp[-2]) ); + CTX.sp -= 3; + continue; + } + +//564 + VM_ENTRY(MP_BC_DELETE_FAST): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_DELETE_FAST", MP_BC_DELETE_FAST); + if (CTX.fastn[-unum] == MP_OBJ_NULL) { + LOCAL_NAME_ERROR(); //goto local_name_error; + } + CTX.fastn[-unum] = MP_OBJ_NULL; + continue; + } +//574 + VM_ENTRY(MP_BC_DELETE_DEREF): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_DELETE_DEREF", MP_BC_DELETE_DEREF); + if (mp_obj_cell_get(CTX.fastn[-unum]) == MP_OBJ_NULL) { + LOCAL_NAME_ERROR(); //goto local_name_error; + } + mp_obj_cell_set(CTX.fastn[-unum], MP_OBJ_NULL); + continue; + } + + VM_ENTRY(MP_BC_DELETE_NAME): { + VM_DECODE_QSTR; + RAISE_IF_NULL( mp_delete_name(CTX.qst) ); + continue; + } +//591 + VM_ENTRY(MP_BC_DELETE_GLOBAL): { + VM_DECODE_QSTR; + VM_TRACE_QSTR("MP_BC_DELETE_GLOBAL", MP_BC_DELETE_GLOBAL); + RAISE_IF_NULL( mp_delete_global(CTX.qst) ); + continue; + } + + VM_ENTRY(MP_BC_DUP_TOP): { + mp_obj_t top = VM_TOP(); + VM_PUSH(top); + continue; + } +//604 + VM_ENTRY(MP_BC_DUP_TOP_TWO): + CTX.sp += 2; + CTX.sp[0] = CTX.sp[-2]; + CTX.sp[-1] = CTX.sp[-3]; + continue; + + VM_ENTRY(MP_BC_POP_TOP): { + CTX.sp -= 1; + continue; } // master misses scoping + + VM_ENTRY(MP_BC_ROT_TWO): { + mp_obj_t top = CTX.sp[0]; + CTX.sp[0] = CTX.sp[-1]; + CTX.sp[-1] = top; + continue; + } +//621 + VM_ENTRY(MP_BC_ROT_THREE): { + mp_obj_t top = CTX.sp[0]; + CTX.sp[0] = CTX.sp[-1]; + CTX.sp[-1] = CTX.sp[-2]; + CTX.sp[-2] = top; + continue; + } + + VM_ENTRY(MP_BC_JUMP): { + VM_DECODE_SLABEL; + VM_TRACE("MP_BC_JUMP", MP_BC_JUMP); + CTX.ip += CTX.slab; + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_POP_JUMP_IF_TRUE): { + VM_DECODE_SLABEL; + if (mp_obj_is_true(VM_POP())) { + CTX.ip += CTX.slab; + } + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_POP_JUMP_IF_FALSE): { + VM_DECODE_SLABEL; + if (!mp_obj_is_true(VM_POP())) { + CTX.ip += CTX.slab; + } + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_JUMP_IF_TRUE_OR_POP): { + VM_DECODE_SLABEL; + if (mp_obj_is_true(VM_TOP())) { + CTX.ip += CTX.slab; + } else { + CTX.sp--; + } + break; //goto pending_exception_check; + } +//661 + VM_ENTRY(MP_BC_JUMP_IF_FALSE_OR_POP): { + VM_DECODE_SLABEL; + if (mp_obj_is_true(VM_TOP())) { + CTX.sp--; + } else { + CTX.ip += CTX.slab; + } + break; //goto pending_exception_check; + } + + VM_ENTRY(MP_BC_POP_EXCEPT_JUMP): { + assert(CTX.exc_sp >= CTX.exc_stack); + VM_POP_EXC_BLOCK(); + VM_DECODE_ULABEL; + CTX.ip += CTX.ulab; + break; //goto pending_exception_check; + } + + +//896 + VM_ENTRY(MP_BC_BUILD_TUPLE): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_BUILD_TUPLE", MP_BC_BUILD_TUPLE); + CTX.sp -= unum - 1; + VM_SET_TOP(mp_obj_new_tuple(unum, CTX.sp)); + RAISE_IF_NULL(VM_TOP()); + continue; + } + + +//905 + VM_ENTRY(MP_BC_BUILD_LIST): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_BUILD_LIST", MP_BC_BUILD_LIST); + CTX.sp -= unum - 1; + VM_SET_TOP(mp_obj_new_list(unum, CTX.sp)); + RAISE_IF_NULL(VM_TOP()); + continue; + } + + +//914 + VM_ENTRY(MP_BC_BUILD_MAP): { + VM_DECODE_UINT; + VM_TRACE("MP_BC_BUILD_MAP", MP_BC_BUILD_MAP); + RAISE_IF_NULL( VM_PUSH(mp_obj_new_dict(unum)) ); + continue; + } + + +//922 + VM_ENTRY(MP_BC_STORE_MAP): { + VM_TRACE("MP_BC_STORE_MAP", MP_BC_STORE_MAP); + CTX.sp -= 2; + RAISE_IF_NULL( mp_obj_dict_store(CTX.sp[0], CTX.sp[2], CTX.sp[1]) ); + continue; + } + + +//671 + VM_ENTRY(MP_BC_SETUP_WITH): { + VM_TRACE("MP_BC_SETUP_WITH", MP_BC_SETUP_WITH); + // stack: (..., ctx_mgr) + mp_obj_t obj = VM_TOP(); + mp_load_method(obj, MP_QSTR___exit__, CTX.sp); + mp_load_method(obj, MP_QSTR___enter__, CTX.sp + 2); + mp_obj_t ret = mp_call_method_n_kw(0, 0, CTX.sp + 2); + RAISE_IF_NULL(ret); + CTX.sp += 1; + VM_PUSH_EXC_BLOCK(1); + VM_PUSH(ret); + // stack: (..., __exit__, ctx_mgr, as_value) + continue; + } + + VM_ENTRY(MP_BC_WITH_CLEANUP): { + VM_TRACE("MP_BC_WITH_CLEANUP", MP_BC_WITH_CLEANUP); + + // Arriving here, there's "exception control block" on top of stack, + // and __exit__ method (with self) underneath it. Bytecode calls __exit__, + // and "deletes" it off stack, shifting "exception control block" + // to its place. + // The bytecode emitter ensures that there is enough space on the Python + // value stack to hold the __exit__ method plus an additional 4 entries. + if (VM_TOP() == mp_const_none) { + // stack: (..., __exit__, ctx_mgr, None) + CTX.sp[1] = mp_const_none; + CTX.sp[2] = mp_const_none; + CTX.sp -= 2; +#pragma message "740: unhandled mp_call_method_n_kw in MP_BC_WITH_CLEANUP" +clog(" 740: mp_call_method_n_kw"); + RAISE_IF_NULL( mp_call_method_n_kw(3, 0, CTX.sp) ); + VM_SET_TOP(mp_const_none); + } else if (mp_obj_is_small_int(VM_TOP())) { + // Getting here there are two distinct cases: + // - unwind return, stack: (..., __exit__, ctx_mgr, ret_val, SMALL_INT(-1)) + // - unwind jump, stack: (..., __exit__, ctx_mgr, dest_ip, SMALL_INT(num_exc)) + // For both cases we do exactly the same thing. + mp_obj_t data = CTX.sp[-1]; + mp_obj_t cause = CTX.sp[0]; + CTX.sp[-1] = mp_const_none; + CTX.sp[0] = mp_const_none; + CTX.sp[1] = mp_const_none; +#pragma message "753: unhandled mp_call_method_n_kw in MP_BC_WITH_CLEANUP" +clog(" 753: mp_call_method_n_kw"); + mp_call_method_n_kw(3, 0, CTX.sp - 3); + CTX.sp[-3] = data; + CTX.sp[-2] = cause; + CTX.sp -= 2; // we removed (__exit__, ctx_mgr) + } else { + assert(mp_obj_is_exception_instance(VM_TOP())); + // stack: (..., __exit__, ctx_mgr, exc_instance) + // Need to pass (exc_type, exc_instance, None) as arguments to __exit__. + CTX.sp[1] = CTX.sp[0]; + CTX.sp[0] = MP_OBJ_FROM_PTR(mp_obj_get_type(CTX.sp[0])); + CTX.sp[2] = mp_const_none; + CTX.sp -= 2; +#pragma message "766: unhandled mp_call_method_n_kw in MP_BC_WITH_CLEANUP" +clog(" 766: mp_call_method_n_kw"); + mp_obj_t ret_value = mp_call_method_n_kw(3, 0, CTX.sp); + if (mp_obj_is_true(ret_value)) { + // We need to silence/swallow the exception. This is done + // by popping the exception and the __exit__ handler and + // replacing it with None, which signals END_FINALLY to just + // execute the finally handler normally. + VM_SET_TOP(mp_const_none); + } else { + // We need to re-raise the exception. We pop __exit__ handler + // by copying the exception instance down to the new top-of-stack. + CTX.sp[0] = CTX.sp[3]; + } + } + continue; + } +//741 ************************************************************************************* + VM_ENTRY(MP_BC_UNWIND_JUMP): { +#if VMTRACE + clog(" 399:MP_BC_UNWIND_JUMP=%i",MP_BC_UNWIND_JUMP); +#endif + VM_DECODE_SLABEL; + VM_PUSH((mp_obj_t)(mp_uint_t)(uintptr_t)(CTX.ip + CTX.slab)); // push destination ip for jump + VM_PUSH((mp_obj_t)(mp_uint_t)(*CTX.ip)); // push number of exception handlers to unwind (0x80 bit set if we also need to pop stack) +unwind_jump:; + mp_uint_t unum = (mp_uint_t)VM_POP(); // get number of exception handlers to unwind + while ((unum & 0x7f) > 0) { + unum -= 1; + assert(CTX.exc_sp >= CTX.exc_stack); + // FIXME () + if (MP_TAGPTR_TAG1(CTX.exc_sp->val_sp)) { + if (CTX.exc_sp->handler > CTX.ip) { + // Found a finally handler that isn't active; run it. + // Getting here the stack looks like: + // (..., X, dest_ip) + // where X is pointed to by exc_sp->val_sp and in the case + // of a "with" block contains the context manager info. + + assert(&CTX.sp[-1] == MP_TAGPTR_PTR(CTX.exc_sp->val_sp)); + + // We're going to run "finally" code as a coroutine + // (not calling it recursively). Set up a sentinel + // on the stack so it can return back to us when it is + // done (when WITH_CLEANUP or END_FINALLY reached). + // The sentinel is the number of exception handlers left to + // unwind, which is a non-negative integer. + VM_PUSH(MP_OBJ_NEW_SMALL_INT(unum)); + CTX.ip = CTX.exc_sp->handler; // get exception handler byte code address +//REMOVED CTX.exc_sp--; // pop exception handler + continue; // goto VM_DISPATCH_loop; // run the exception handler + } else { + // Found a finally handler that is already active; cancel it. + VM_CANCEL_ACTIVE_FINALLY(CTX.sp); + } + } + VM_POP_EXC_BLOCK(); + } + CTX.ip = (const byte*)MP_OBJ_TO_PTR(VM_POP()); // pop destination ip for jump + if (unum != 0) { + // pop the exhausted iterator + CTX.sp -= MP_OBJ_ITER_BUF_NSLOTS; + } + break; //DISPATCH_WITH_PEND_EXC_CHECK() : goto pending_exception_check; + } +//784 + VM_ENTRY(MP_BC_SETUP_EXCEPT): +clog(" 833:vmswitch.c EXCEPT"); + + VM_ENTRY(MP_BC_SETUP_FINALLY): { + +#if VMTRACE +clog(" 451:MP_BC_SETUP_FINALLY=%i",MP_BC_SETUP_FINALLY); +#endif + #if SELECTIVE_EXC_IP + VM_PUSH_EXC_BLOCK((CTX.code_state->ip[-1] == MP_BC_SETUP_FINALLY) ? 1 : 0); + #else + VM_PUSH_EXC_BLOCK((CTX.code_state->ip[0] == MP_BC_SETUP_FINALLY) ? 1 : 0); + #endif + continue; + } +//795 + VM_ENTRY(MP_BC_END_FINALLY): { +#if VMTRACE +clog(" 463:MP_BC_END_FINALLY=%i",MP_BC_END_FINALLY); +#endif + // if VM_TOP is None, just pops it and continues + // if VM_TOP is an integer, finishes coroutine and returns control to caller + // if VM_TOP is an exception, reraises the exception + if (VM_TOP() == mp_const_none) { + assert(CTX.exc_sp >= CTX.exc_stack); + VM_POP_EXC_BLOCK(); + CTX.sp--; + } else if (mp_obj_is_small_int(VM_TOP())) { + // We finished "finally" coroutine and now VM_DISPATCH back + // to our caller, based on TOS value + mp_int_t cause = MP_OBJ_SMALL_INT_VALUE(VM_POP()); + if (cause < 0) { + // A negative cause indicates unwind return + goto unwind_return; + } else { + // Otherwise it's an unwind jump and we must push as a raw + // number the number of exception handlers to unwind + VM_PUSH((mp_obj_t)cause); + goto unwind_jump; + } + } else { + assert(mp_obj_is_exception_instance(VM_TOP())); + RAISE(VM_TOP()); + } + continue; + } + + +//823 + VM_ENTRY(MP_BC_GET_ITER): { + VM_SET_TOP(mp_getiter(VM_TOP(), NULL)); + continue; + } + + +//829 + // An iterator for a for-loop takes MP_OBJ_ITER_BUF_NSLOTS slots on + // the Python value stack. These slots are either used to store the + // iterator object itself, or the first slot is MP_OBJ_NULL and + // the second slot holds a reference to the iterator object. + VM_ENTRY(MP_BC_GET_ITER_STACK): { + mp_obj_t obj = VM_TOP(); + mp_obj_iter_buf_t *iter_buf = (mp_obj_iter_buf_t*)CTX.sp; + CTX.sp += MP_OBJ_ITER_BUF_NSLOTS - 1; + obj = mp_getiter(obj, iter_buf); + RAISE_IF_NULL(obj); + if (obj != MP_OBJ_FROM_PTR(iter_buf)) { + // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. + CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] = MP_OBJ_NULL; + CTX.sp[-MP_OBJ_ITER_BUF_NSLOTS + 2] = obj; + } + continue; + } + + +//955 + VM_ENTRY(MP_BC_STORE_COMP): { + VM_DECODE_UINT; + mp_obj_t obj = CTX.sp[-(unum >> 2)]; + if ((unum & 3) == 0) { + mp_obj_list_append(obj, CTX.sp[0]); + CTX.sp--; + } else if (!MICROPY_PY_BUILTINS_SET || (unum & 3) == 1) { + mp_obj_dict_store(obj, CTX.sp[0], CTX.sp[-1]); + CTX.sp -= 2; + #if MICROPY_PY_BUILTINS_SET + } else { + mp_obj_set_store(obj, CTX.sp[0]); + CTX.sp--; + #endif + } + continue; + } + + +//974 + VM_ENTRY(MP_BC_UNPACK_SEQUENCE): { + VM_DECODE_UINT; + RAISE_IF_NULL( mp_unpack_sequence(CTX.sp[0], unum, CTX.sp) ); + CTX.sp += unum - 1; + continue; + } + + +//982 + VM_ENTRY(MP_BC_UNPACK_EX): { + VM_DECODE_UINT; + RAISE_IF_NULL( mp_unpack_ex(CTX.sp[0], unum, CTX.sp) ); + CTX.sp += (unum & 0xff) + ((unum >> 8) & 0xff); + continue; + } + + +//990 + VM_ENTRY(MP_BC_MAKE_FUNCTION): { + VM_DECODE_PTR; + VM_PUSH(mp_make_function_from_raw_code(CTX.ptr, MP_OBJ_NULL, MP_OBJ_NULL)); + continue; + } + + +//996 + VM_ENTRY(MP_BC_MAKE_FUNCTION_DEFARGS): { + VM_DECODE_PTR; + // Stack layout: def_tuple def_dict <- TOS + mp_obj_t def_dict = VM_POP(); + VM_SET_TOP(mp_make_function_from_raw_code(CTX.ptr, VM_TOP(), def_dict)); + continue; + } + + +//1024 + VM_ENTRY(MP_BC_MAKE_CLOSURE): { + VM_DECODE_PTR; + size_t n_closed_over = *CTX.ip++; + // Stack layout: closed_overs <- TOS + CTX.sp -= n_closed_over - 1; + VM_SET_TOP(mp_make_closure_from_raw_code(CTX.ptr, n_closed_over, CTX.sp)); + continue; + } + + +//1013 + VM_ENTRY(MP_BC_MAKE_CLOSURE_DEFARGS): { + VM_DECODE_PTR; + size_t n_closed_over = *CTX.ip++; + // Stack layout: def_tuple def_dict closed_overs <- TOS + CTX.sp -= 2 + n_closed_over - 1; + VM_SET_TOP(mp_make_closure_from_raw_code(CTX.ptr, 0x100 | n_closed_over, CTX.sp)); + continue; + } + + +//1022 + /* + ENTRY(MP_BC_CALL_FUNCTION): { +included + }*/ + + +//1111 +// ENTRY(MP_BC_CALL_METHOD): { + #include "vmsl/vmbc_call_method.c" +//} + + +//1154 + VM_ENTRY(MP_BC_CALL_METHOD_VAR_KW): { + FRAME_UPDATE(); + + VM_DECODE_UINT; + // unum & 0xff == n_positional + // (unum >> 8) & 0xff == n_keyword + // We have following stack layout here: + // fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS + CTX.sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 3; + #if MICROPY_STACKLESS + + if (mp_obj_get_type(*CTX.sp) == &mp_type_fun_bc) { + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + mp_call_args_t out_args; + mp_call_prepare_args_n_kw_var(true, unum, CTX.sp, &out_args); + + mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, + out_args.n_args, out_args.n_kw, out_args.args); + #if !MICROPY_ENABLE_PYSTACK + // Freeing args at this point does not follow a LIFO order so only do it if + // pystack is not enabled. For pystack, they are freed when code_state is. + mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); + #endif + #if !MICROPY_ENABLE_PYSTACK + if (new_state == NULL) { + // Couldn't allocate codestate on heap: in the strict case raise + // an exception, otherwise just fall through to stack allocation. + #if MICROPY_STACKLESS_STRICT + goto deep_recursion_error; +/* +deep_recursion_error: + mp_raise_recursion_depth(); + RAISE_IF(true); +*/ + #endif + } else + #endif + { + new_state->prev = CTX.code_state; + CTX.code_state = new_state; + goto run_code_state; + } + } + #endif + VM_SET_TOP(mp_call_method_n_kw_var(true, unum, CTX.sp)); + continue; + } +//1205 + VM_ENTRY(MP_BC_RETURN_VALUE): { + +unwind_return: +#if VMTRACE + clog(" 652:MP_BC_RETURN_VALUE=%i",MP_BC_RETURN_VALUE); +#endif + // Search for and execute finally handlers that aren't already active + while (CTX.exc_sp >= CTX.exc_stack) { + if (MP_TAGPTR_TAG1(CTX.exc_sp->val_sp)) { + if (CTX.exc_sp->handler > CTX.ip) { +#if VMTRACE + clog(" 682:Found a finally handler that isn't active."); +#endif + // Getting here the stack looks like: + // (..., X, [iter0, iter1, ...,] ret_val) + // where X is pointed to by exc_sp->val_sp and in the case + // of a "with" block contains the context manager info. + // There may be 0 or more for-iterators between X and the + // return value, and these must be removed before control can + // pass to the finally code. We simply copy the ret_value down + // over these iterators, if they exist. If they don't then the + // following is a null operation. + mp_obj_t *finally_sp = MP_TAGPTR_PTR(CTX.exc_sp->val_sp); + finally_sp[1] = CTX.sp[0]; + CTX.sp = &finally_sp[1]; + // We're going to run "finally" code as a coroutine + // (not calling it recursively). Set up a sentinel + // on a stack so it can return back to us when it is + // done (when WITH_CLEANUP or END_FINALLY reached). + VM_PUSH(MP_OBJ_NEW_SMALL_INT(-1)); + CTX.ip = CTX.exc_sp->handler; + + //BEWARE cannot "continue" or "break" we're inside the while@670 + goto VM_DISPATCH_loop; + + } else { +#if VMTRACE + clog(" 697:Found a finally handler that is already active; cancel it."); +#endif + VM_CANCEL_ACTIVE_FINALLY(CTX.sp); + } + } + VM_POP_EXC_BLOCK(); + } // end while + + CTX.code_state->sp = CTX.sp; + assert(CTX.exc_sp == CTX.exc_stack - 1); + + MICROPY_VM_HOOK_RETURN + + #if MICROPY_STACKLESS + if (CTX.code_state->prev != NULL) { + mp_obj_t res = *CTX.sp; + mp_globals_set(CTX.code_state->old_globals); + mp_code_state_t *new_code_state = CTX.code_state->prev; + #if MICROPY_ENABLE_PYSTACK + // Free code_state, and args allocated by mp_call_prepare_args_n_kw_var + // (The latter is implicitly freed when using pystack due to its LIFO nature.) + // The sizeof in the following statement does not include the size of the variable + // part of the struct. This arg is anyway not used if pystack is enabled. + mp_nonlocal_free(CTX.code_state, sizeof(mp_code_state_t)); + #endif + CTX.code_state = new_code_state; + *CTX.code_state->sp = res; + goto run_code_state_from_return; + } + #endif + + VM_return(MP_VM_RETURN_NORMAL); + } + +//1266 +//VM_ENTRY(MP_BC_RAISE_LAST): + +//1282 +//VMENTRY(MP_BC_RAISE_OBJ): + +//1288 +//VM_ENTRY(MP_BC_RAISE_FROM): + + #include "vmsl/vmbc_raise.c" + + + + +//1295 +//yield: + VM_ENTRY(MP_BC_YIELD_VALUE): + CTX.code_state->ip = CTX.ip; + CTX.code_state->sp = CTX.sp; + CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + + VM_return(MP_VM_RETURN_YIELD); +//1304 no continuation + + + + + + + + VM_ENTRY(MP_BC_YIELD_FROM): { + +#define EXC_MATCH(exc, type) mp_obj_exception_match(exc, type) +#define GENERATOR_EXIT_IF_NEEDED(t) if (t != MP_OBJ_NULL && EXC_MATCH(t, MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { mp_obj_t raise_t = mp_make_raise_obj(t); RAISE(raise_t); } + mp_vm_return_kind_t ret_kind; + mp_obj_t send_value = VM_POP(); + mp_obj_t t_exc = MP_OBJ_NULL; + mp_obj_t ret_value; + +//NO_NLR CTX.code_state->sp = CTX.sp; // Save sp because it's needed if mp_resume raises StopIteration + + if (CTX.inject_exc != MP_OBJ_NULL) { + t_exc = CTX.inject_exc; + CTX.inject_exc = MP_OBJ_NULL; +clog("1320:vmswitch.c mp_resume + exc"); + ret_kind = mp_resume(VM_TOP(), MP_OBJ_NULL, t_exc, &ret_value); + } else { +clog("1323:vmswitch.c mp_resume"); + ret_kind = mp_resume(VM_TOP(), send_value, MP_OBJ_NULL, &ret_value); + } + + if (ret_kind == MP_VM_RETURN_YIELD) { +clog("1178:vmswitch.c yield from\n"); + CTX.ip--; + VM_PUSH(ret_value); + //goto yield; +//inline 1296-1303 (MP_BC_YIELD_VALUE) +CTX.code_state->ip = CTX.ip; +CTX.code_state->sp = CTX.sp; +CTX.code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(CTX.exc_stack, CTX.exc_sp); + +VM_return(MP_VM_RETURN_YIELD); + + } // no continuation + + if (ret_kind == MP_VM_RETURN_NORMAL) { + // Pop exhausted gen + CTX.sp--; + if (ret_value == MP_OBJ_STOP_ITERATION) { + // Optimize StopIteration + // TODO: get StopIteration's value + VM_PUSH(mp_const_none); + } else { + VM_PUSH(ret_value); + } + + // If we injected GeneratorExit downstream, then even + // if it was swallowed, we re-raise GeneratorExit + GENERATOR_EXIT_IF_NEEDED(t_exc); + continue; + } // no continuation + + + assert(ret_kind == MP_VM_RETURN_EXCEPTION); + // Pop exhausted gen + CTX.sp--; + if (EXC_MATCH(ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + VM_PUSH(mp_obj_exception_get_value(ret_value)); + // If we injected GeneratorExit downstream, then even + // if it was swallowed, we re-raise GeneratorExit + GENERATOR_EXIT_IF_NEEDED(t_exc); + continue; + } // no continuation + + RAISE(ret_value); + } + +#include "vmsl/vmbc_import.c" // FAIL + +#if MICROPY_PY_BUILTINS_SET + VM_ENTRY(MP_BC_BUILD_SET): { + + VM_DECODE_UINT; + CTX.sp -= unum - 1; + VM_SET_TOP(mp_obj_new_set(unum, CTX.sp)); + RAISE_IF_NULL(VM_TOP()); + continue; + } +#endif + +#if MICROPY_PY_BUILTINS_SLICE + VM_ENTRY(MP_BC_BUILD_SLICE): { + + mp_obj_t step = mp_const_none; + if (*CTX.ip++ == 3) { + // 3-argument slice includes step + step = VM_POP(); + } + mp_obj_t stop = VM_POP(); + mp_obj_t start = VM_TOP(); + VM_SET_TOP(mp_obj_new_slice(start, stop, step)); + RAISE_IF_NULL(VM_TOP()); + continue; + } +#endif + + #include "vmsl/vmbc_for_iter.c" + + #include "vmsl/vmbc_call_function_var_kw.c" + + + #include "vmsl/vmbc_call_function.c" // FAIL + + + + + + +//!MICROPY_OPT_COMPUTED_GOTO + + +/* + VM_ENTRY(MP_BC_RAISE_VARARGS): { +clog("mpsl:998 MP_BC_RAISE_VARARGS\n"); + + mp_uint_t unum = *CTX.ip; + mp_obj_t obj; + if (unum == 2) { + mp_warning(NULL, "exception chaining not supported"); + // ignore (pop) "from" argument + CTX.sp--; + } + if (unum == 0) { + // search for the inner-most previous exception, to reraise it + obj = MP_OBJ_NULL; + for (mp_exc_stack_t *e = CTX.exc_sp; e >= CTX.exc_stack; e--) { + if (e->prev_exc != NULL) { + obj = MP_OBJ_FROM_PTR(e->prev_exc); + break; + } + } + if (obj == MP_OBJ_NULL) { + obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "no active exception to reraise"); + RAISE(obj); + } + } else { + obj = VM_TOP(); + } + obj = mp_make_raise_obj(obj); + RAISE(obj); + } +*/ + + + +//1421 +//ENTRY_DEFAULT: + default: { +#if VM_TRACE + clog(" 928:ENTRY_DEFAULT"); +#endif + //if (CTX.ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + 64) { + if (CTX.ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM) { + VM_PUSH(MP_OBJ_NEW_SMALL_INT((mp_int_t)CTX.ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16)); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_LOAD_FAST_MULTI + 16) { + obj_shared = CTX.fastn[MP_BC_LOAD_FAST_MULTI - (mp_int_t)CTX.ip[-1]]; + if (obj_shared == MP_OBJ_NULL) + LOCAL_NAME_ERROR(); + VM_PUSH(obj_shared); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_STORE_FAST_MULTI + 16) { + CTX.fastn[MP_BC_STORE_FAST_MULTI - (mp_int_t)CTX.ip[-1]] = VM_POP(); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { + VM_SET_TOP(mp_unary_op(CTX.ip[-1] - MP_BC_UNARY_OP_MULTI, VM_TOP())); + continue; + } // no continuation + + if (CTX.ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { + mp_obj_t rhs = VM_POP(); + mp_obj_t lhs = VM_TOP(); + VM_SET_TOP(mp_binary_op(CTX.ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); + continue; + } // no continuation + + clog("1453:FATAL OPCODE N/I"); + mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NotImplementedError, "opcode"); + CTX.code_state->state[0] = obj; + + VM_return(MP_VM_RETURN_EXCEPTION); + // no continuation + } + +//1460 + + + + + diff --git a/ports/wapy-wasm/vmsl/vmwarmup.c b/ports/wapy-wasm/vmsl/vmwarmup.c new file mode 100644 index 000000000..31a9c05ef --- /dev/null +++ b/ports/wapy-wasm/vmsl/vmwarmup.c @@ -0,0 +1,67 @@ +if (VMOP < VMOP_INIT) { + //puts("init"); + + Py_Init(); + + stack_initial = __builtin_frame_address(0); // could also use alloca(0) + stack_max = (uintptr_t)stack_initial - stack_limit; + + + VMOP = VMOP_INIT; + + entry_point[0]=JMP_NONE; + exit_point[0]=JMP_NONE; + come_from[0]=0; + type_point[0]=0; + + for (int i=0; i +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#if defined(__EMSCRIPTEN__) || defined(__WASI__) + #include "emscripten.h" +#elif __CPP__ + #define EMSCRIPTEN_KEEPALIVE +#endif + +#include "../wapy/upython.h" + +#include + +#if defined(__EMSCRIPTEN__) +#define wa_clock_gettime(clockid, timespec) clock_gettime(clockid, timespec) +#include +#define wa_gettimeofday(timeval, tmz) gettimeofday(timeval, tmz) +#endif + + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + +extern struct timespec t_timespec; +extern struct timeval t_timeval; + + +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + + +uint64_t mp_hal_time_ns(void) { + // wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + // return (uint64_t)( t_timespec.tv_sec + t_timespec.tv_sec ); + + wa_gettimeofday(&t_timeval, NULL); + return (uint64_t)t_timeval.tv_sec * 1000000000ULL + (uint64_t)t_timeval.tv_usec * 1000ULL; +} + + +mp_uint_t mp_hal_ticks_ms(void) { + #if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0) && defined(_POSIX_MONOTONIC_CLOCK) + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + return t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000; + #else + wa_gettimeofday(&t_timeval, NULL); + return t_timeval.tv_sec * 1000 + t_timeval.tv_usec / 1000; + #endif +} + + +mp_uint_t mp_hal_ticks_us(void) { + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long now_us = t_timespec.tv_sec * 1000000 + t_timespec.tv_nsec / 1000 ; + #if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); + #else + return (mp_uint_t)now_us; + #endif +} + + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + cdbg("mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + //if (c>127) + // fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + //if (c>127) + // fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick +extern int io_encode_hex; +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { +#if defined(__EMSCRIPTEN__) +#else // WASI/node + if (!io_encode_hex) { + printf("%s", str); + return; + } +#endif + for(int i=0;i +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#include "../wapy/upython.h" + +#include + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + +struct timespec ts; +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + +mp_uint_t mp_hal_ticks_ms(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us -1000; + return (mp_uint_t)( (now_us - epoch_us) / 1000 ) ; +#else + return (mp_uint_t)(now_us / 1000); +#endif + +} + +mp_uint_t mp_hal_ticks_us(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); +#else + return (mp_uint_t)now_us; +#endif + +} + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + fprintf(stderr,"mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + if (c>127) + fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + if (c>127) + fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick + +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + for(int i=0;i +#include + +#include "py/runtime.h" + +#pragma message "TODO: maybe take the function name as an argument so we can print nicer error messages" +int mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { + // TODO maybe take the function name as an argument so we can print nicer error messages + + // The reverse of MP_OBJ_FUN_MAKE_SIG + bool takes_kw = sig & 1; + size_t n_args_min = sig >> 17; + size_t n_args_max = (sig >> 1) & 0xffff; + + if (n_kw && !takes_kw) { + + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_TypeError_o(MP_ERROR_TEXT("function doesn't take keyword arguments")); + #endif + return 1; + } + + if (n_args_min == n_args_max) { + if (n_args != n_args_min) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), + n_args_min, n_args)); + #endif + return 1; + + } + } else { + if (n_args < n_args_min) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function missing %d required positional arguments", + n_args_min - n_args)); + #endif + return 1; + } else if (n_args > n_args_max) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function expected at most %d arguments, got %d", + n_args_max, n_args)); + #endif + return 1; + } + } + + return 0; +} + + +int mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals) { + size_t pos_found = 0, kws_found = 0; + for (size_t i = 0; i < n_allowed; i++) { + mp_obj_t given_arg; + if (i < n_pos) { + if (allowed[i].flags & MP_ARG_KW_ONLY) { + goto extra_positional; + } + pos_found++; + given_arg = pos[i]; + } else { + mp_map_elem_t *kw = mp_map_lookup(kws, MP_OBJ_NEW_QSTR(allowed[i].qst), MP_MAP_LOOKUP); + if (kw == NULL) { + if (allowed[i].flags & MP_ARG_REQUIRED) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%q' argument required", allowed[i].qst)); + #endif + return 1; + } + out_vals[i] = allowed[i].defval; + continue; + } else { + kws_found++; + given_arg = kw->value; + } + } + if ((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_BOOL) { + out_vals[i].u_bool = mp_obj_is_true(given_arg); + } else if ((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_INT) { + out_vals[i].u_int = mp_obj_get_int(given_arg); + } else { + assert((allowed[i].flags & MP_ARG_KIND_MASK) == MP_ARG_OBJ); + out_vals[i].u_obj = given_arg; + } + } + if (pos_found < n_pos) { + extra_positional: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_arg_error_terse_mismatch(); + #else + // TODO better error message + mp_raise_TypeError_o("extra positional arguments given"); + return 1; + #endif + } + if (kws_found < kws->used) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_arg_error_terse_mismatch(); + } else { + // TODO better error message + mp_raise_TypeError_o("extra keyword arguments given"); + return 1; + } + } + return 0; +} + +int mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals) { + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_pos); + return mp_arg_parse_all(n_pos, args, &kw_args, n_allowed, allowed, out_vals); +} + +void mp_arg_error_terse_mismatch(void) { + mp_raise_TypeError_o("argument num/types mismatch"); +} + +#if MICROPY_CPYTHON_COMPAT +void mp_arg_error_unimpl_kw(void) { + mp_raise_NotImplementedError_o("keyword argument(s) not yet implemented - use normal args instead"); +} +#endif diff --git a/ports/wapy/builtinimport_no_nlr.c b/ports/wapy/builtinimport_no_nlr.c new file mode 100644 index 000000000..5310e9a79 --- /dev/null +++ b/ports/wapy/builtinimport_no_nlr.c @@ -0,0 +1,524 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2019 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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 "py/compile.h" +#include "py/objmodule.h" +#include "py/persistentcode.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/frozenmod.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_PRINT (0) +#define DEBUG_printf(...) (void)0 +#endif + +#if MICROPY_ENABLE_EXTERNAL_IMPORT + +#define PATH_SEP_CHAR '/' + +bool mp_obj_is_package(mp_obj_t module) { + mp_obj_t dest[2]; + mp_load_method_maybe(module, MP_QSTR___path__, dest); + return dest[0] != MP_OBJ_NULL; +} + +// Stat either frozen or normal module by a given path +// (whatever is available, if at all). +STATIC mp_import_stat_t mp_import_stat_any(const char *path) { + #if MICROPY_MODULE_FROZEN + mp_import_stat_t st = mp_frozen_stat(path); + if (st != MP_IMPORT_STAT_NO_EXIST) { + return st; + } + #endif + return mp_import_stat(path); +} + +STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { + mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); + if (stat == MP_IMPORT_STAT_FILE) { + return stat; + } + + #if MICROPY_PERSISTENT_CODE_LOAD + vstr_ins_byte(path, path->len - 2, 'm'); + stat = mp_import_stat_any(vstr_null_terminated_str(path)); + if (stat == MP_IMPORT_STAT_FILE) { + return stat; + } + #endif + + return MP_IMPORT_STAT_NO_EXIST; +} + +STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { + mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); + DEBUG_printf("stat %s: %d\n", vstr_str(path), stat); + if (stat == MP_IMPORT_STAT_DIR) { + return stat; + } + + // not a directory, add .py and try as a file + vstr_add_str(path, ".py"); + return stat_file_py_or_mpy(path); +} + +STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *dest) { + #if MICROPY_PY_SYS + // extract the list of paths + size_t path_num; + mp_obj_t *path_items; + mp_obj_list_get(mp_sys_path, &path_num, &path_items); + + if (path_num != 0) { + // go through each path looking for a directory or file + for (size_t i = 0; i < path_num; i++) { + vstr_reset(dest); + size_t p_len; + const char *p = mp_obj_str_get_data(path_items[i], &p_len); + if (p_len > 0) { + vstr_add_strn(dest, p, p_len); + vstr_add_char(dest, PATH_SEP_CHAR); + } + vstr_add_strn(dest, file_str, file_len); + mp_import_stat_t stat = stat_dir_or_file(dest); + if (stat != MP_IMPORT_STAT_NO_EXIST) { + return stat; + } + } + + // could not find a directory or file + return MP_IMPORT_STAT_NO_EXIST; + } + #endif + + // mp_sys_path is empty, so just use the given file name + vstr_add_strn(dest, file_str, file_len); + return stat_dir_or_file(dest); +} + +#if MICROPY_MODULE_FROZEN_STR || MICROPY_ENABLE_COMPILER +STATIC int do_load_from_lexer(mp_obj_t module_obj, mp_lexer_t *lex) { + #if MICROPY_PY___FILE__ + qstr source_name = lex->source_name; + mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + #endif + + // parse, compile and execute the module in its context + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + return mp_parse_compile_execute(lex, MP_PARSE_FILE_INPUT, mod_globals, mod_globals) == MP_OBJ_NULL; +} +#endif + +#if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_MODULE_FROZEN_MPY +STATIC int do_execute_raw_code(mp_obj_t module_obj, mp_raw_code_t *raw_code, const char *source_name) { + (void)source_name; + + #if MICROPY_PY___FILE__ + mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(qstr_from_str(source_name))); + #endif + + // execute the module in its context + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + + // save context + mp_obj_dict_t *volatile old_globals = mp_globals_get(); + mp_obj_dict_t *volatile old_locals = mp_locals_get(); + + // set new context + mp_globals_set(mod_globals); + mp_locals_set(mod_globals); + + mp_obj_t module_fun = mp_make_function_from_raw_code(raw_code, MP_OBJ_NULL, MP_OBJ_NULL); + module_fun = mp_call_function_0(module_fun); + + // restore context + mp_globals_set(old_globals); + mp_locals_set(old_locals); + + return module_fun == MP_OBJ_NULL; +} +#endif + +STATIC int do_load(mp_obj_t module_obj, vstr_t *file) { + #if MICROPY_MODULE_FROZEN || MICROPY_ENABLE_COMPILER || (MICROPY_PERSISTENT_CODE_LOAD && MICROPY_HAS_FILE_READER) + char *file_str = vstr_null_terminated_str(file); + #endif + + // If we support frozen modules (either as str or mpy) then try to find the + // requested filename in the list of frozen module filenames. + #if MICROPY_MODULE_FROZEN + void *modref; + int frozen_type = mp_find_frozen_module(file_str, file->len, &modref); + #endif + + // If we support frozen str modules and the compiler is enabled, and we + // found the filename in the list of frozen files, then load and execute it. + #if MICROPY_MODULE_FROZEN_STR + if (frozen_type == MP_FROZEN_STR) { + return do_load_from_lexer(module_obj, modref); + } + #endif + + // If we support frozen mpy modules and we found a corresponding file (and + // its data) in the list of frozen files, execute it. + #if MICROPY_MODULE_FROZEN_MPY + if (frozen_type == MP_FROZEN_MPY) { + return do_execute_raw_code(module_obj, modref, file_str); + } + #endif + + // If we support loading .mpy files then check if the file extension is of + // the correct format and, if so, load and execute the file. + #if MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD + if (file_str[file->len - 3] == 'm') { + mp_raw_code_t *raw_code = mp_raw_code_load_file(file_str); + if (raw_code == NULL) { + return 1; + } + return do_execute_raw_code(module_obj, raw_code, file_str); + } + #endif + + // If we can compile scripts then load the file and compile and execute it. + #if MICROPY_ENABLE_COMPILER + { + mp_lexer_t *lex = mp_lexer_new_from_file(file_str); + return do_load_from_lexer(module_obj, lex); + } + #else + // If we get here then the file was not frozen and we can't compile scripts. + mp_raise_msg_o(&mp_type_ImportError, "script compilation not supported"); + return 1; + #endif +} + +STATIC void chop_component(const char *start, const char **end) { + const char *p = *end; + while (p > start) { + if (*--p == '.') { + *end = p; + return; + } + } + *end = p; +} + +mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { + #if DEBUG_PRINT + DEBUG_printf("__import__:\n"); + for (size_t i = 0; i < n_args; i++) { + DEBUG_printf(" "); + mp_obj_print(args[i], PRINT_REPR); + DEBUG_printf("\n"); + } + #endif + + mp_obj_t module_name = args[0]; + mp_obj_t fromtuple = mp_const_none; + mp_int_t level = 0; + if (n_args >= 4) { + fromtuple = args[3]; + if (n_args >= 5) { + level = MP_OBJ_SMALL_INT_VALUE(args[4]); + if (level < 0) { + mp_raise_ValueError(NULL); + } + } + } + + size_t mod_len; + const char *mod_str = mp_obj_str_get_data(module_name, &mod_len); + if (mod_str == NULL) { + return MP_OBJ_NULL; + } + + if (level != 0) { + // What we want to do here is to take name of current module, + // chop trailing components, and concatenate with passed-in + // module name, thus resolving relative import name into absolute. + // This even appears to be correct per + // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name + // "Relative imports use a module's __name__ attribute to determine that + // module's position in the package hierarchy." + level--; + mp_obj_t this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); + if (this_name_q == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + assert(this_name_q != MP_OBJ_NULL); + #if MICROPY_CPYTHON_COMPAT + if (MP_OBJ_QSTR_VALUE(this_name_q) == MP_QSTR___main__) { + // This is a module run by -m command-line switch, get its real name from backup attribute + this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + if (this_name_q == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + #endif + mp_map_t *globals_map = &mp_globals_get()->map; + mp_map_elem_t *elem = mp_map_lookup(globals_map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); + bool is_pkg = (elem != NULL); + + #if DEBUG_PRINT + DEBUG_printf("Current module/package: "); + mp_obj_print(this_name_q, PRINT_REPR); + DEBUG_printf(", is_package: %d", is_pkg); + DEBUG_printf("\n"); + #endif + + size_t this_name_l; + const char *this_name = mp_obj_str_get_data(this_name_q, &this_name_l); + + const char *p = this_name + this_name_l; + if (!is_pkg) { + // We have module, but relative imports are anchored at package, so + // go there. + chop_component(this_name, &p); + } + + while (level--) { + chop_component(this_name, &p); + } + + // We must have some component left over to import from + if (p == this_name) { + return mp_raise_ValueError_o("cannot perform relative import"); + } + + uint new_mod_l = (mod_len == 0 ? (size_t)(p - this_name) : (size_t)(p - this_name) + 1 + mod_len); + char *new_mod = mp_local_alloc(new_mod_l); + memcpy(new_mod, this_name, p - this_name); + if (mod_len != 0) { + new_mod[p - this_name] = '.'; + memcpy(new_mod + (p - this_name) + 1, mod_str, mod_len); + } + + qstr new_mod_q = qstr_from_strn(new_mod, new_mod_l); + mp_local_free(new_mod); + DEBUG_printf("Resolved base name for relative import: '%s'\n", qstr_str(new_mod_q)); + module_name = MP_OBJ_NEW_QSTR(new_mod_q); + mod_str = qstr_str(new_mod_q); + mod_len = new_mod_l; + } + + if (mod_len == 0) { + mp_raise_ValueError(NULL); + } + + // check if module already exists + qstr module_name_qstr = mp_obj_str_get_qstr(module_name); + mp_obj_t module_obj = mp_module_get(module_name_qstr); + if (module_obj != MP_OBJ_NULL) { + DEBUG_printf("Module already loaded\n"); + // If it's not a package, return module right away + char *p = strchr(mod_str, '.'); + if (p == NULL) { + return module_obj; + } + // If fromlist is not empty, return leaf module + if (fromtuple != mp_const_none) { + return module_obj; + } + // Otherwise, we need to return top-level package + qstr pkg_name = qstr_from_strn(mod_str, p - mod_str); + return mp_module_get(pkg_name); + } + DEBUG_printf("Module not yet loaded\n"); + + uint last = 0; + VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX) + module_obj = MP_OBJ_NULL; + mp_obj_t top_module_obj = MP_OBJ_NULL; + mp_obj_t outer_module_obj = MP_OBJ_NULL; + uint i; + for (i = 1; i <= mod_len; i++) { + if (i == mod_len || mod_str[i] == '.') { + // create a qstr for the module name up to this depth + qstr mod_name = qstr_from_strn(mod_str, i); + DEBUG_printf("Processing module: %s\n", qstr_str(mod_name)); + DEBUG_printf("Previous path: =%.*s=\n", vstr_len(&path), vstr_str(&path)); + + // find the file corresponding to the module name + mp_import_stat_t stat; + if (vstr_len(&path) == 0) { + // first module in the dotted-name; search for a directory or file + stat = find_file(mod_str, i, &path); + } else { + // latter module in the dotted-name; append to path + vstr_add_char(&path, PATH_SEP_CHAR); + vstr_add_strn(&path, mod_str + last, i - last); + stat = stat_dir_or_file(&path); + } + DEBUG_printf("Current path: %.*s\n", vstr_len(&path), vstr_str(&path)); + + if (stat == MP_IMPORT_STAT_NO_EXIST) { + module_obj = MP_OBJ_NULL; + #if MICROPY_MODULE_WEAK_LINKS + // check if there is a weak link to this module + if (i == mod_len) { + module_obj = mp_module_search_umodule(mod_str); + if (module_obj != MP_OBJ_NULL) { + // found weak linked module + mp_module_call_init(mod_name, module_obj); + } + } + #endif + if (module_obj == MP_OBJ_NULL) { + // couldn't find the file, so fail + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_msg_o(&mp_type_ImportError, "module not found"); + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ImportError, + "no module named '%q'", mod_name)); + } + } + } else { + // found the file, so get the module + module_obj = mp_module_get(mod_name); + } + + if (module_obj == MP_OBJ_NULL) { + // module not already loaded, so load it! + + module_obj = mp_obj_new_module(mod_name); + + // if args[3] (fromtuple) has magic value False, set up + // this module for command-line "-m" option (set module's + // name to __main__ instead of real name). Do this only + // for *modules* however - packages never have their names + // replaced, instead they're -m'ed using a special __main__ + // submodule in them. (This all apparently is done to not + // touch package name itself, which is important for future + // imports). + if (i == mod_len && fromtuple == mp_const_false && stat != MP_IMPORT_STAT_DIR) { + mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj); + mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + #if MICROPY_CPYTHON_COMPAT + // Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules). + mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj); + // Store real name in "__main__" attribute. Chosen semi-randonly, to reuse existing qstr's. + mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(mod_name)); + #endif + } + + if (stat == MP_IMPORT_STAT_DIR) { + DEBUG_printf("%.*s is dir\n", vstr_len(&path), vstr_str(&path)); + // https://docs.python.org/3/reference/import.html + // "Specifically, any module that contains a __path__ attribute is considered a package." + mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(&path), vstr_len(&path))); + size_t orig_path_len = path.len; + vstr_add_char(&path, PATH_SEP_CHAR); + vstr_add_str(&path, "__init__.py"); + if (stat_file_py_or_mpy(&path) != MP_IMPORT_STAT_FILE) { + //mp_warning("%s is imported as namespace package", vstr_str(&path)); + } else { + if (do_load(module_obj, &path)) { + // exception + return MP_OBJ_NULL; + } + } + path.len = orig_path_len; + } else { // MP_IMPORT_STAT_FILE + if (do_load(module_obj, &path)) { + // exception + return MP_OBJ_NULL; + } + // This should be the last component in the import path. If there are + // remaining components then it's an ImportError because the current path + // (the module that was just loaded) is not a package. This will be caught + // on the next iteration because the file will not exist. + } + } + if (outer_module_obj != MP_OBJ_NULL) { + qstr s = qstr_from_strn(mod_str + last, i - last); + mp_store_attr(outer_module_obj, s, module_obj); + } + outer_module_obj = module_obj; + if (top_module_obj == MP_OBJ_NULL) { + top_module_obj = module_obj; + } + last = i + 1; + } + } + + // If fromlist is not empty, return leaf module + if (fromtuple != mp_const_none) { + return module_obj; + } + // Otherwise, we need to return top-level package + return top_module_obj; +} + +#else // MICROPY_ENABLE_EXTERNAL_IMPORT + +mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { + // Check that it's not a relative import + if (n_args >= 5 && MP_OBJ_SMALL_INT_VALUE(args[4]) != 0) { + return mp_raise_NotImplementedError_o("relative import"); + } + + // Check if module already exists, and return it if it does + qstr module_name_qstr = mp_obj_str_get_qstr(args[0]); + if (module_name_qstr == MP_QSTR_NULL) { + return MP_OBJ_NULL; + } + mp_obj_t module_obj = mp_module_get(module_name_qstr); + if (module_obj != MP_OBJ_NULL) { + return module_obj; + } + + #if MICROPY_MODULE_WEAK_LINKS + // Check if there is a weak link to this module + module_obj = mp_module_search_umodule(qstr_str(module_name_qstr)); + if (module_obj != MP_OBJ_NULL) { + // Found weak-linked module + mp_module_call_init(module_name_qstr, module_obj); + return module_obj; + } + #endif + + // Couldn't find the module, so fail + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_msg_o(&mp_type_ImportError, "module not found"); + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ImportError, + "no module named '%q'", module_name_qstr)); + } +} + +#endif // MICROPY_ENABLE_EXTERNAL_IMPORT + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin___import___obj, 1, 5, mp_builtin___import__); diff --git a/ports/wapy/cmod/common/_zipfile.pym b/ports/wapy/cmod/common/_zipfile.pym new file mode 100644 index 000000000..e078b9636 --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile.pym @@ -0,0 +1,69 @@ +#include "zip.h" + +class ZipFile(type): + hash : uint8_t = 0 + zbuf : void_p = NULL + zbuf_size : size_t = 0 + zip = "struct zip_t *" + path = ('char_p','NULL') + +// TODO: return self + def open(name: const_char_p="")->void: + zip_entry_open(self.zip, name); + + def read()-> bytes: + zip_entry_read(self.zip, &self.zbuf, &self.zbuf_size); + zip_entry_close(self.zip); + try: + if (self.zbuf): + return self.zbuf + finally: + free(self.zbuf) + + def init(path : const_char_p="") -> void: + self.path = strdup(path) + self.hash = strlen(path) + printf("ZIPFILE[%d;%s]self\n", self.hash, self.path) + self.zip = zip_open(self.path, 0, 'r'); + + def filename() -> bytes: + return self.path + + def close() -> void: + if self.path: + zip_close(self.zip); + free(self.path) + + async def pouet() -> int: + if 1: + printf(" pouet arg0 %zu\n", argc ); + return 42; + + async def pouet2() -> int: +/* + for (iterator = 0; iterator < 5; iterator++) { + printf("gen2 %s ",self.name); + yield(iterator); + } +*/ + if 1: + if 2: + return 43; + + + + +#>>> import zipfile; x= zipfile.ZipFile() +#>>> x.init("/data/cross/pydk-applications/org.beerware.wapy/app/build/outputs/apk/debug/app-debug.apk") +#>>> x.read("assets/python3/python3.py") +# x.read_from("assets/python3/python3.py",zp) + + + + + + + + + + diff --git a/ports/wapy/cmod/common/_zipfile/micropython.mk b/ports/wapy/cmod/common/_zipfile/micropython.mk new file mode 100644 index 000000000..46fb21c7e --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/micropython.mk @@ -0,0 +1,8 @@ + +_ZIPFILE_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(wildcard $(_ZIPFILE_MOD_DIR)/*.c) + +# add module folder to include paths if needed +CFLAGS_USERMOD += -I$(_ZIPFILE_MOD_DIR) diff --git a/ports/wapy/cmod/common/_zipfile/miniz.h b/ports/wapy/cmod/common/_zipfile/miniz.h new file mode 100644 index 000000000..c4fcfb83e --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/miniz.h @@ -0,0 +1,6854 @@ +/* + miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP + reading/writing/appending, PNG writing See "unlicense" statement at the end + of this file. Rich Geldreich , last updated Oct. 13, + 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: + http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the + archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of + all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major + release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug + (thanks kahmyong.moon@hp.com) which could cause locate files to not find + files. This bug would only have occured in earlier versions if you explicitly + used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or + mz_zip_add_mem_to_archive_file_in_place() (which used this flag). If you + can't switch to v1.15 but want to fix this bug, just remove the uses of this + flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when + pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract + compressed data from directory entries, to account for weird zipfiles which + contain zero-size compressed data on dir entries. Hopefully this fix won't + cause any issues on weird zip archives, because it assumes the low 16-bits of + zip external attributes are DOS attributes (which I believe they always are + in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the + internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, + tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping + (super useful for OpenGL apps), and explicit control over the compression + level (so you can set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), + tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG + file. + - Modified example2 to help test the + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix + possible src file fclose() leak if alignment bytes+local header file write + faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the + wrong central dir header offset, appears harmless in this release, but it + became a problem in the zip64 branch 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 + compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix + mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and + re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze + (static analysis) option and fixed all warnings (except for the silly "Use of + the comma-operator in a tested expression.." analysis warning, which I + purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and + tested Linux executables. The codeblocks workspace is compatible with + Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app + derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on + ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level + functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the + MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which + are too large (a limitation of the examples, not miniz itself). 4/12/12 v1.12 + - More comments, added low-level example5.c, fixed a couple minor + level_and_flags issues in the archive API's. level_and_flags can now be set + to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson + for the feedback/bug report. 5/28/11 v1.11 - Added statement from + unlicense.org 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput + now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 + vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved + compression perf. issues on some file types. + - Refactored the compression code for better readability and + maintainability. + - Added level 10 compression level (L10 has slightly better ratio than + level 9, but could have a potentially large drop in throughput on some + files). 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, + and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, + and Huffman-only streams. It performs and compresses approximately as well as + zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is + implemented as a single function coroutine: see tinfl_decompress(). It + supports decompression into a 32KB (or larger power of 2) wrapping buffer, or + into a memory block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory + allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough + functionality present for it to be a drop-in zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly + routines. Supports raw deflate streams or standard zlib streams with adler-32 + checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or + zlib static dictionaries. I've tried to closely emulate zlib's various + flavors of stream flushing and return status codes, but there are no + guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, + originally written by Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in + mind, with just enough abstraction to get the job done with minimal fuss. + There are simple API's to retrieve file information, read files from existing + archives, create new archives, append new files to existing archives, or + clone archive data from one archive to another. It supports archives located + in memory or the heap, on disk (using stdio.h), or you can specify custom + file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a + disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const + char *pArchive_name, size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an + archive, the entire central directory is located and read as-is into memory, + and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a + loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one + example) can be used to identify multiple versions of the same file in an + archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using + mz_zip_reader_get_num_files()) and retrieve detailed info on each file by + calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer + immediately writes compressed file data to disk and builds an exact image of + the central directory in memory. The central directory image is written all + at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file + data to any power of 2 alignment, which can be useful when the archive will + be read from optical media. Also, the writer supports placing arbitrary data + blobs at the very beginning of ZIP archives. Archives written using either + feature are still readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is + to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, + const char *pArchive_name, const void *pBuf, size_t buf_size, const void + *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be + appended to. Note the appending is done in-place and is not an atomic + operation, so if something goes wrong during the operation it's possible the + archive could be left without a central directory (although the local file + headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, + cloning only those bits you want to preserve into a new archive using using + the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and + rename the newly written archive, and you're done. This is safe but requires + a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using + mz_zip_writer_init_from_reader(), append new files as needed, then finalize + the archive which will write an updated central directory to the original + archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() + does.) There's a possibility that the archive's central directory could be + lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle + unencrypted, stored or deflated files. Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, + either cut and paste the below header, or create miniz.h, #define + MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your + target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define + MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before + including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), + stat64(), etc. Otherwise you won't be able to process large files (i.e. + 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be +// CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on +// stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able +// to get the current time, or get/set file times, and the C run-time funcs that +// get/set times won't be called. The current downside is the times written to +// your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive +// API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression +// API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent +// conflicts against stock zlib. +//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom +// user alloc/free/realloc callbacks to the zlib and archive API's, and a few +// stand-alone helper API's which don't provide custom user functions (such as +// tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +// TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc +// on Linux +#define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ + defined(__i386) || defined(__i486__) || defined(__i486) || \ + defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient + * integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || \ + defined(_LP64) || defined(__LP64__) || defined(__ia64__) || \ + defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are +// reasonably fast (and don't involve compiler generated calls to helper +// functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __APPLE__ +#define ftello64 ftello +#define fseeko64 fseeko +#define fopen64 fopen +#define freopen64 freopen + +// Darwin OSX +#define MZ_PLATFORM 19 +#endif + +#ifndef MZ_PLATFORM +#if defined(_WIN64) || defined(_WIN32) || defined(__WIN32__) +#define MZ_PLATFORM 0 +#else +// UNIX +#define MZ_PLATFORM 3 +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some +// parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() +// unless you've modified the MZ_MALLOC macro) to release a block allocated from +// the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with +// ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with +// ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or + * modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: +// items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, + size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The +// other values are for advanced use (refer to the zlib docs). +enum { + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best +// possible compression (not zlib compatible, and may be very slow), +// MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s { + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func + zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been +// optimized purely for performance, not ratio. (This special func. is +// currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and +// MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with +// zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no +// header or footer) mem_level must be between [1, 9] (it's checked but +// ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, + int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as +// calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input +// and producing as much output as possible. Parameters: +// pStream is the stream to read from and write to. You must initialize/update +// the next_in, avail_in, next_out, and avail_out members. flush may be +// MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not +// available, and/or there's more output to be written but the output buffer +// is full). MZ_STREAM_END if all input has been consumed and all output bytes +// have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or +// output buffers are empty. (Fill up the input buffer or free up some output +// space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of +// data that could be generated by deflate(), assuming flush is set to only +// MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on +// failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of +// data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that +// controls the window size and whether or not the stream has been wrapped with +// a zlib header/footer: window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse +// zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the +// input as needed, and writing as much to the output as possible. Parameters: +// pStream is the stream to read from and write to. You must initialize/update +// the next_in, avail_in, next_out, and avail_out members. flush may be +// MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. On the first call, if flush is +// MZ_FINISH it's assumed the input and output buffers are both sized large +// enough to decompress the entire stream in a single call (this is slightly +// faster). MZ_FINISH implies that there are no more source bytes available +// beside what's already in the input buffer, and that the output buffer is +// large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or +// there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes +// have been written. For zlib streams, the adler-32 of the decompressed data +// has also been verified. MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is +// empty but the inflater needs more input to continue, or if the output +// buffer is not large enough. Call mz_inflate() again with more input data, +// or with more room in the output buffer (except when using single call +// decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on +// failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the +// error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used +// as a drop-in replacement for the subset of zlib that miniz.c supports. Define +// MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib +// in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional +// expression is constant" message. +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum { + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct { + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +typedef struct { + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, + mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, + size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, + mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, + mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, + mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is +// 0 this function returns the number of bytes needed to fully store the +// filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, + char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, + mz_uint file_index, void *pBuf, + size_t buf_size, mz_uint flags, + void *pUser_read_buf, + size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc( + mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, + mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, + void *pBuf, size_t buf_size, + mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, + const char *pFilename, void *pBuf, + size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, + size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, + const char *pFilename, size_t *pSize, + mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, + mz_uint file_index, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, + const char *pFilename, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and +// modified times. This function only extracts files, not archive directory +// records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, + const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, + const char *pArchive_filename, + const char *pDst_filename, + mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive +// file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, + size_t size_to_reserve_at_beginning, + size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient +// in-place file appends to occur on an existing archive. For archives opened +// using mz_zip_reader_init_file, pFilename must be the archive's filename so it +// can be reopened for writing. If the file can't be reopened, +// mz_zip_reader_end() will be called. For archives opened using +// mz_zip_reader_init_mem, the memory block must be growable using the realloc +// callback (which defaults to realloc unless you've overridden it). Finally, +// for archives opened using mz_zip_reader_init, the mz_zip_archive's user +// provided m_pWrite function cannot be NULL. Note: In-place archive +// modification is not recommended unless you know what you're doing, because if +// execution stops or something goes wrong before the archive is finalized the +// file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, + const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record +// the current local time into the archive. To add a directory entry, call this +// method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, +// MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or +// just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, + const void *pBuf, size_t buf_size, + mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, + const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, + mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records +// the disk file's modified time into the archive. level_and_flags - compression +// level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd +// with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, + const char *pSrc_filename, const void *pComment, + mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint32 ext_attributes); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no +// recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, + mz_zip_archive *pSource_zip, + mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by +// the end of central directory record. After an archive is finalized, the only +// valid call on the mz_zip_archive struct is mz_zip_writer_end(). An archive +// must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, + size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if +// mz_zip_writer_init_file() was used. Note for the archive to be valid, it must +// have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) +// appends a memory blob to a ZIP archive. level_and_flags - compression level +// (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero +// or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place( + const char *pZip_filename, const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, + const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and +// ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the +// input is a raw deflate stream. TINFL_FLAG_HAS_MORE_INPUT: If set, there are +// more input bytes available beyond the end of the supplied input buffer. If +// clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large +// enough to hold the entire decompressed stream. If clear, the output buffer is +// at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the +// decompressed bytes. +enum { + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block +// allocated via malloc(). On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data +// to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger +// than src_buf_len on uncompressible data. The caller must call mz_free() on +// the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block +// in memory. Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the +// number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an +// internal 32KB buffer, and a user provided callback function will be called to +// flush the buffer. Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum { + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) \ + do { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function +// actually needed for decompression. All the other functions are just +// high-level helpers for improved usability. This is a universal API, i.e. it +// can be used as a building block to build any desired higher level +// decompression API. In the limit case, it can be called once per every byte +// input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, + const mz_uint8 *pIn_buf_next, + size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, + mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, + const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum { + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct { + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], + m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag { + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, + m_check_adler32, m_dist, m_counter, m_num_extra, + m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], + m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly +// slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain +// the max. number of probes per dictionary search): TDEFL_DEFAULT_MAX_PROBES: +// The compressor defaults to 128 dictionary probes per dictionary search. +// 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ +// (slowest/best compression). +enum { + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before +// the deflate data, and the Adler-32 of the source data at the end. Otherwise, +// you'll get raw deflate data. TDEFL_COMPUTE_ADLER32: Always compute the +// adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more +// efficient lazy parsing. TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to +// decrease the compressor's initialization time to the minimum, but the output +// may vary from run to run given the same input (depending on the contents of +// memory). TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a +// distance of 1) TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per +// dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum { + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block +// allocated via malloc(). On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against +// the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger +// than src_buf_len on uncompressible data. The caller must free() the returned +// block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in +// memory. Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be +// 1, 2, 3, or 4. The image pitch in bytes per scanline will be w*num_chans. +// The leftmost pixel on the top scanline is stored first in memory. level may +// range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, +// MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL If flip is +// true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be +// larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, + int h, int num_chans, + size_t *pLen_out, + mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, + int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write +// compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, + void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The +// above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +enum { + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed +// output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#else +enum { + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#endif + +// The low-level tdefl functions below may be used directly if the above helper +// functions aren't flexible enough. The low-level functions don't make any heap +// allocations, unlike the above helper functions. +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct { + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, + m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, + m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, + m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not +// dynamically allocate memory. pBut_buf_func: If NULL, output data will be +// supplied to the specified callback. In this case, the user should call the +// tdefl_compress_buffer() API for compression. If pBut_buf_func is NULL the +// user should always call the tdefl_compress() API. flags: See the above enums +// (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer +// as possible, and writing as much compressed data to the specified output +// buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, + size_t *pIn_buf_size, void *pOut_buf, + size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a +// non-NULL tdefl_put_buf_func_ptr. tdefl_compress_buffer() always consumes the +// entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, + size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't +// defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be +// much slower on some files) window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, +// MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, + int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want +// the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#include +#include + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) \ + ((mz_uint32)(((const mz_uint8 *)(p))[0]) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) \ + ((mz_uint32)(((const mz_uint8 *)(p))[0]) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) \ + (((mz_uint64)MZ_READ_LE32(p)) | \ + (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) \ + << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C +// implementation that balances processor cache usage against speed": +// http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { + static const mz_uint32 s_crc32[16] = { + 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, + 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c}; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; +} + +void mz_free(void *p) { MZ_FREE(p); } + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +static void def_free_func(void *opaque, void *address) { + (void)opaque, (void)address; + MZ_FREE(address); +} +static void *def_realloc_func(void *opaque, void *address, size_t items, + size_t size) { + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) { return MZ_VERSION; } + +int mz_deflateInit(mz_streamp pStream, int level) { + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, + MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, + int mem_level, int strategy) { + tdefl_compressor *pComp; + mz_uint comp_flags = + TDEFL_COMPUTE_ADLER32 | + tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || + ((window_bits != MZ_DEFAULT_WINDOW_BITS) && + (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, + sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) { + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || + (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, + ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) { + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || + (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == + TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, + pStream->next_in, &in_bytes, pStream->next_out, + &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) { + mz_status = MZ_STREAM_ERROR; + break; + } else if (defl_status == TDEFL_STATUS_DONE) { + mz_status = MZ_STREAM_END; + break; + } else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { + if ((flush) || (pStream->total_in != orig_total_in) || + (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty + // tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, + 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len, int level) { + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len) { + return mz_compress2(pDest, pDest_len, pSource, source_len, + MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) { + return mz_deflateBound(NULL, source_len); +} + +typedef struct { + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) { + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && + (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, + sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) { + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) { + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) { + // MZ_FINISH on the first call implies that the input and output buffers are + // large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, + pStream->next_out, pStream->next_out, &out_bytes, + decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && + (!pState->m_dict_avail)) + ? MZ_STREAM_END + : MZ_OK; + } + + for (;;) { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress( + &pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, + pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some + // uncompressed data left in the output dictionary - + // oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress + // without supplying more input or by setting flush + // to MZ_FINISH. + else if (flush == MZ_FINISH) { + // The output buffer MUST be large to hold the remaining uncompressed data + // when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's + // at least 1 more byte on the way. If there's no more room left in the + // output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || + (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) + ? MZ_STREAM_END + : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len) { + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR + : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) { + static struct { + int m_err; + const char *m_pDesc; + } s_error_descs[] = {{MZ_OK, ""}, + {MZ_STREAM_END, "stream end"}, + {MZ_NEED_DICT, "need dictionary"}, + {MZ_ERRNO, "file error"}, + {MZ_STREAM_ERROR, "stream error"}, + {MZ_DATA_ERROR, "data error"}, + {MZ_MEM_ERROR, "out of memory"}, + {MZ_BUF_ERROR, "buf error"}, + {MZ_VERSION_ERROR, "version error"}, + {MZ_PARAM_ERROR, "parameter error"}}; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all +// compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do { \ + for (;;) { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt +// to read beyond the input buf, then something is wrong with the input because +// the inflator never reads ahead more than it needs to. Currently +// TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) \ + do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for (;;) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes +// remaining in the input buffer falls below 2. It reads just enough bytes from +// the input stream that are needed to decode the next Huffman code (and +// absolutely no more). It works by trying to fully decode a Huffman code by +// using whatever bits are currently present in the bit buffer. If this fails, +// it reads another byte, and tries again until it succeeds or until the bit +// buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex +// than you would initially expect because the zlib API expects the decompressor +// to never read beyond the final byte of the deflate stream. (In other words, +// when this macro wants to read another byte from the input, it REALLY needs +// another byte in order to fully decode the next Huffman code.) Handling this +// properly is particularly important on raw deflate (non-zlib) streams, which +// aren't followed by a byte aligned adler-32. The slow path is only executed at +// the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | \ + (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= \ + 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, + const mz_uint8 *pIn_buf_next, + size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, + mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, + const mz_uint32 decomp_flags) { + static const int s_length_base[31] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const int s_length_extra[31] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, + 4, 4, 5, 5, 5, 5, 0, 0, 0}; + static const int s_dist_base[32] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, + 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, + 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0}; + static const int s_dist_extra[32] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + static const mz_uint8 s_length_dezigzag[19] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + static const int s_min_table_sizes[3] = {257, 1, 4}; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = + pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = + pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = + (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) + ? (size_t)-1 + : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, + dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer + // is large enough to hold the entire output file (in which case it doesn't + // matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || + (pOut_buf_next < pOut_buf_start)) { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || + (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || + ((out_buf_size_mask + 1) < + (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != + (mz_uint)(0xFFFF ^ + (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } else { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), + (size_t)(pIn_buf_end - pIn_buf_cur)), + counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } else if (r->m_type == 3) { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } else { + if (r->m_type == 1) { + mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } else { + for (counter = 0; counter < 3; counter++) { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], + total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; + sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { + mz_uint rev_code = 0, l, cur_code, + code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == + (tree_cur = pTable->m_look_up[rev_code & + (TINFL_FAST_LOOKUP_SIZE - 1)])) { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = + (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) { + for (counter = 0; + counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, + (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, + r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, + r->m_len_codes + r->m_table_sizes[0], + r->m_table_sizes[1]); + } + } + for (;;) { + mz_uint8 *pSrc; + for (;;) { + if (((pIn_buf_end - pIn_buf_cur) < 4) || + ((pOut_buf_end - pOut_buf_cur) < 2)) { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } else { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = + r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= + 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0] + .m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = + r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= + 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0] + .m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && + (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { + while (counter--) { + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = + pOut_buf_start[(dist_from_out_buf_start++ - dist) & + out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) { + if (counter) { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_SKIP_BITS(32, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf; + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & + (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && + (status >= 0)) { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, + s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && + (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && + (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags) { + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) { + size_t src_buf_size = src_buf_len - src_buf_ofs, + dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress( + &decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, + (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, + &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags) { + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = + tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, + (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED + : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, + dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = + tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, + &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && + (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression +// API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, + 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, + 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, + 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, + 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, + 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, + 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, + 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, + 285}; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0}; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17}; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, + 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29}; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13}; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted +// values. +typedef struct { + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, + tdefl_sym_freq *pSyms0, + tdefl_sym_freq *pSyms1) { + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = + pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, +// alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) { + if (leaf >= n || A[root].m_key < A[leaf].m_key) { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) { + while (root >= 0 && (int)A[root].m_key == dpth) { + used++; + root--; + } + while (avbl > used) { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, + int code_list_len, + int max_code_size) { + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, + int table_len, int code_size_limit, + int static_table) { + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } else { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], + *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, + code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)( \ + d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = \ + (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) { + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, + rle_repeat_count, packed_code_sizes_index; + mz_uint8 + code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], + sizeof(mz_uint8) * num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], + sizeof(mz_uint8) * num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, + sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = + (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } else if (++rle_repeat_count == 6) { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { + TDEFL_RLE_PREV_CODE_SIZE(); + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes + [2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS( + d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; + packed_code_sizes_index < num_packed_code_sizes;) { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], + "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) { + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && \ + MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; + flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], + match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], + d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], + num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; + flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], + match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + if (match_dist < 512) { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } else { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && + // MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) { + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = + ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && + (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = + ((d->m_pPut_buf_func == NULL) && + ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) + ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) + : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = + tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || + (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output + // buffer and send a raw block instead. + if (((use_raw_block) || + ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= + d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) { + TDEFL_PUT_BITS( + d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], + 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed + // block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) { + if (flush == TDEFL_FINISH) { + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } else { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, + sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, + sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { + if (d->m_pPut_buf_func) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } else if (pOutput_buf_start == d->m_output_buf) { + int bytes_to_copy = (int)MZ_MIN( + (size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, + bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } else { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) ((p)[0] | (p)[1] << 8) +static MZ_FORCEINLINE void +tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, + mz_uint max_match_len, mz_uint *pMatch_dist, + mz_uint *pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, + match_len = *pMatch_len, probe_pos = pos, next_probe_pos, + probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), + s01 = *s; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || \ + ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (*q != s01) + continue; + p = s; + probe_len = 32; + do { + } while ((*(++p) == *(++q)) && (*(++p) == *(++q)) && (*(++p) == *(++q)) && + (*(++p) == *(++q)) && (--probe_len > 0)); + if (!probe_len) { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); + break; + } else if ((probe_len = ((mz_uint)(p - s) * 2) + + (mz_uint)(*(const mz_uint8 *)p == + *(const mz_uint8 *)q)) > match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == + max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void +tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, + mz_uint max_match_len, mz_uint *pMatch_dist, + mz_uint *pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, + match_len = *pMatch_len, probe_pos = pos, next_probe_pos, + probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || \ + ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && \ + (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) { + // Faster, minimally featured LZRW1-style match+parse loop with better + // register utilization. Intended for applications where raw throughput is + // valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, + lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, + total_lz_bytes = d->m_total_lz_bytes, + num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = + (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN( + d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, + MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = + (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & + TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= + dict_size) && + ((mz_uint32)( + *(d->m_dict + (probe_pos & TDEFL_LZ_DICT_SIZE_MASK)) | + (*(d->m_dict + ((probe_pos & TDEFL_LZ_DICT_SIZE_MASK) + 1)) + << 8) | + (*(d->m_dict + ((probe_pos & TDEFL_LZ_DICT_SIZE_MASK) + 2)) + << 16)) == first_trigram)) { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = + (const mz_uint16 *)(d->m_dict + + (probe_pos & TDEFL_LZ_DICT_SIZE_MASK)); + mz_uint32 probe_len = 32; + do { + } while ((*(++p) == *(++q)) && (*(++p) == *(++q)) && + (*(++p) == *(++q)) && (*(++p) == *(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || + ((cur_match_len == TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 8U * 1024U))) { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } else { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 1) && + (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - + TDEFL_MIN_MATCH_LEN]]++; + } + } else { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, + mz_uint8 lit) { + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void +tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && + (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) { + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to + // TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & + TDEFL_LZ_DICT_SIZE_MASK, + ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] + << TDEFL_LZ_HASH_SHIFT) ^ + d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN( + src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } else { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & + TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] + << (TDEFL_LZ_HASH_SHIFT * 2)) ^ + (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] + << TDEFL_LZ_HASH_SHIFT) ^ + c) & + (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = + MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = + d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } else { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, + d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 8U * 1024U)) || + (cur_pos == cur_match_dist) || + ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) { + if (cur_match_len > d->m_saved_match_len) { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } else { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } else if (!cur_match_dist) + tdefl_record_literal(d, + d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || + (cur_match_len >= 128)) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output + // buffer. + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && + (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= + d->m_total_lz_bytes) || + (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { + if (d->m_pIn_buf_size) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, + d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, + d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE + : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, + size_t *pIn_buf_size, void *pOut_buf, + size_t *pOut_buf_size, tdefl_flush flush) { + if (!d) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == + ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || + (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || + (pIn_buf_size && *pIn_buf_size && !pIn_buf) || + (pOut_buf_size && *pOut_buf_size && !pOut_buf)) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | + TDEFL_RLE_MATCHES)) == 0)) { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && + (pIn_buf)) + d->m_adler32 = + (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, + d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && + (!d->m_output_flush_remaining)) { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, + size_t in_buf_size, tdefl_flush flush) { + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = + d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = + d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, + sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, + sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == + TDEFL_STATUS_OKAY); + succeeded = + succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == + TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct { + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, + void *pUser) { + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output( + pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output( + pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = {0, 1, 6, 32, 16, 32, + 128, 256, 512, 768, 1500}; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we +// want a bit more compression and it's fine if throughput to fall off a cliff +// on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, + int strategy) { + mz_uint comp_flags = + s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | + ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif // MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) // nonstandard extension used : non-constant + // aggregate initializer (also supported by GNU + // C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public +// domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files +// generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, + int h, int num_chans, + size_t *pLen_out, + mz_uint level, mz_bool flip) { + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was + // defined. + static const mz_uint s_tdefl_png_num_probes[11] = { + 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500}; + tdefl_compressor *pComp = + (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) { + MZ_FREE(pComp); + return NULL; + } + // write dummy header + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, + s_tdefl_png_num_probes[MZ_MIN(10, level)] | + TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, + (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, + bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != + TDEFL_STATUS_DONE) { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + // write real header + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41] = {0x89, + 0x50, + 0x4e, + 0x47, + 0x0d, + 0x0a, + 0x1a, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x49, + 0x48, + 0x44, + 0x52, + 0, + 0, + (mz_uint8)(w >> 8), + (mz_uint8)w, + 0, + 0, + (mz_uint8)(h >> 8), + (mz_uint8)h, + 8, + chans[num_chans], + 0, + 0, + 0, + 0, + 0, + 0, + 0, + (mz_uint8)(*pLen_out >> 24), + (mz_uint8)(*pLen_out >> 16), + (mz_uint8)(*pLen_out >> 8), + (mz_uint8)*pLen_out, + 0x49, + 0x44, + 0x41, + 0x54}; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter( + "\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, + *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, + int num_chans, size_t *pLen_out) { + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we + // can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's + // where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, + pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#include + +#if defined(_MSC_VER) +static FILE *mz_fopen(const char *pFilename, const char *pMode) { + FILE *pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { + FILE *pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && _LARGEFILE64_SOURCE +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#else +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#else +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler +// alignment and platform endian issues, miniz.c doesn't use structs for any of +// this stuff. +enum { + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct { + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag { + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. + */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 + * will also be slammed to true too, even if we didn't find a zip64 end of + * central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write + * helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) \ + (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) \ + ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, + mz_zip_array *pArray) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t min_new_capacity, + mz_uint growing) { + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, + pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t new_capacity, + mz_uint growing) { + if (new_capacity > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t new_size, + mz_uint growing) { + if (new_size > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t n) { + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, + mz_zip_array *pArray, + const void *pElements, + size_t n) { + if (0 == n) + return MZ_TRUE; + if (!pElements) + return MZ_FALSE; + + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, + pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(time_t time, mz_uint16 *pDOS_time, + mz_uint16 *pDOS_date) { +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, + time_t *pTime) { + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= + * 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, + time_t modified_time) { + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, + mz_zip_error err_num) { + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, + mz_uint32 flags) { + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, + sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, + sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, + sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool +mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, + const mz_zip_array *pCentral_dir_offsets, + mz_uint l_index, mz_uint r_index) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, + l_index)), + *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), + r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central +// directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), +// but it could allocate memory.) +static void +mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) { + int child, root = start; + for (;;) { + if ((child = (root << 1) + 1) >= size) + break; + child += + (((child + 1) < size) && + (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + start--; + } + + end = size - 1; + while (end > 0) { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) { + if ((child = (root << 1) + 1) >= end) + break; + child += + (((child + 1) < end) && + mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, + mz_uint32 record_sig, + mz_uint32 record_size, + mz_int64 *pOfs) { + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = + MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) { + int i, + n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" + * (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= + (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, + mz_uint flags) { + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, + cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = + ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = + (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first + * 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig( + pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { + if (pZip->m_pRead(pZip->m_pIO_opaque, + cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, + pZip64_locator, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { + zip64_end_of_central_dir_ofs = MZ_READ_LE64( + pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > + (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, + pZip64_end_of_central_dir, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = + MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) { + mz_uint32 zip64_total_num_of_disks = + MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < + (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = + (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough + * for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && + ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another + * heap block to hold the unsorted central dir file record offsets, and + * possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, + MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, + pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) { + if (!mz_zip_array_resize(pZip, + &pZip->m_pState->m_sorted_central_dir_offsets, + pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, + pZip->m_pState->m_central_dir.m_p, + cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some + * basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { + mz_uint total_header_size, disk_index, bit_flags, filename_size, + ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || + (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + i) = + (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, + mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == + MZ_UINT32_MAX)) { + /* Attempt to find zip64 extended information field in the entry's extra + * data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) { + const mz_uint8 *pExtra_data; + void *buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > + n) { + buf = MZ_MALLOC(ext_data_size); + if (buf == NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, + cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + filename_size, + buf, ext_data_size) != ext_data_size) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8 *)buf; + } else { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > + extra_size_remaining) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { + /* Ok, the archive didn't have any zip64 headers but it uses a + * zip64 extended information field so mark it as zip64 anyway + * (this can occur with infozip's zip util when it reads + * compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = + extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext + * data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && + (decomp_size != comp_size)) || + (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || + ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > + n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, + mz_uint32 flags) { + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) + ? 0 + : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, + size_t size, mz_uint32 flags) { + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint32 flags) { + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 * +mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, + mz_uint file_index) { + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, + mz_uint file_index) { + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, + // which wasn't correct. Most/all zip writers (hopefully) set DOS + // file/directory attributes in the low 16-bits, so check for the DOS + // directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, + mz_zip_archive_file_stat *pStat) { + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = + mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), + MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, + p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), + n); + pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, + char *pFilename, mz_uint filename_buf_size) { + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { + if (filename_buf_size) + pFilename[0] = '\0'; + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, + const char *pB, + mz_uint len, + mz_uint flags) { + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int +mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, + const mz_zip_array *pCentral_dir_offsets, + mz_uint l_index, const char *pR, mz_uint r_len) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, + l_index)), + *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, + const char *pFilename) { + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) { + int m = (l + h) >> 1, file_index = pIndices[m], + comp = + mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, + file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags) { + mz_uint file_index; + size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && + (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); + if (name_len > 0xFFFF) + return -1; + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > 0xFFFF) + return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = + (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), + file_comment_len = + MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || + (!mz_zip_reader_string_equal(pComment, pFile_comment, + file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { + int ofs = filename_len - 1; + do { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || + (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && + (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, + mz_uint file_index, void *pBuf, + size_t buf_size, mz_uint flags, + void *pUser_read_buf, + size_t user_read_buf_size) { + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, + out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips + // with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have + // compressed deflate data which inflates to 0 bytes, but these entries claim + // to uncompress to 512 bytes in the headers). I'm torn how to handle this + // case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size + : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, + (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, + (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input + // buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else if (pUser_read_buf) { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } else { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && + (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do { + size_t in_buf_size, + out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress( + &inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, + (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | + (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, + (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc( + mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, + mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, + flags, pUser_read_buf, + user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, + void *pBuf, size_t buf_size, + mz_uint flags) { + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, + flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, + const char *pFilename, void *pBuf, + size_t buf_size, mz_uint flags) { + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, + buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, + size_t *pSize, mz_uint flags) { + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == + (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, + flags)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, + const char *pFilename, size_t *pSize, + mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, + mz_uint file_index, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags) { + int status = TINFL_STATUS_DONE; + mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, + out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips + // with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have + // compressed deflate data which inflates to 0 bytes, but these entries claim + // to uncompress to 512 bytes in the headers). I'm torn how to handle this + // case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input + // buffer. + if (pZip->m_pState->m_pMem) { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && + (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && + (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, + (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = + (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, + (size_t)file_stat.m_comp_size); + // cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + // comp_remaining = 0; + } else { + while (comp_remaining) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32( + file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } else { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else { + do { + mz_uint8 *pWrite_buf_cur = + (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, + out_buf_size = + TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress( + &inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, + (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, + comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != + out_buf_size) { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = + (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || + (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && + (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || + (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, + const char *pFilename, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, + flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, + const void *pBuf, size_t n) { + (void)ofs; + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, + const char *pDst_filename, + mz_uint flags) { + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback( + pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) { + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); + } +#endif + + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, + const char *pArchive_filename, + const char *pDst_filename, + mz_uint flags) { + int file_index = + mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || + (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, + sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, + sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, + sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if ((!n) || + ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) + return 0; + + if (new_size > pState->m_mem_capacity) { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + while (new_capacity < new_size) + new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc( + pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, + size_t size_to_reserve_at_beginning, + size_t initial_allocation_size) { + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, + size_to_reserve_at_beginning))) { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint64 size_to_reserve_at_beginning) { + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) { + mz_uint64 cur_ofs = 0; + char buf[4096]; + MZ_CLEAR_OBJ(buf); + do { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, + const char *pFilename) { + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max + // size + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) { +#ifdef MINIZ_NO_STDIO + pFilename; + return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == + (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is + // NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } else if (pState->m_pMem) { + // Archive lives in a memory block. Assume it's from the heap that we can + // resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the + // user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory + // location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, + const void *pBuf, size_t buf_size, + mz_uint level_and_flags) { + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, + level_and_flags, 0, 0); +} + +typedef struct { + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, + void *pUser) { + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, + pState->m_cur_archive_file_ofs, pBuf, + len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header( + mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, + mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, + mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, + mz_uint16 dos_time, mz_uint16 dos_date) { + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header( + mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, + mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, + mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, + mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { + (void)pZip; + mz_uint16 version_made_by = 10 * MZ_VER_MAJOR + MZ_VER_MINOR; + version_made_by |= (MZ_PLATFORM << 8); + + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_MADE_BY_OFS, version_made_by); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir( + mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, + mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, + mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, + mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, + mz_uint32 ext_attributes) { + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || + (((mz_uint64)pState->m_central_dir.m_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header( + pZip, central_dir_header, filename_size, extra_size, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, + dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, + filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, + extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, + comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, + ¢ral_dir_ofs, 1))) { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { + // Basic ZIP archive filename validity checks: Valid filenames cannot start + // with a forward slash, cannot contain a drive letter, and cannot use + // DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint +mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & + (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, + mz_uint64 cur_file_ofs, mz_uint32 n) { + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, + const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, + mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32) { + mz_uint32 ext_attributes = 0; + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = + ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || + (!pArchive_name) || ((comment_size) && (!pComment)) || + (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an + // allocation fails the file remains unmodified. (A good idea if we're doing + // an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size)) || + (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + + sizeof(local_dir_header))) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + cur_archive_file_ofs += + num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { + uncomp_crc32 = + (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, + buf_size) != buf_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } else if (buf_size) { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != + TDEFL_STATUS_DONE)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header( + pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, + comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir( + pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, + const char *pSrc_filename, const void *pComment, + mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint32 ext_attributes) { + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + time_t file_modified_time; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs, uncomp_size = 0, + comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || + ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return MZ_FALSE; + mz_zip_time_t_to_dos_time(file_modified_time, &dos_time, &dos_date); + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + + sizeof(local_dir_header))) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + cur_archive_file_ofs += + num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = + pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) { + while (uncomp_remaining) { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || + (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, + n) != n)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = + (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } else { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for (;;) { + size_t in_buf_size = + (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32( + uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, + uncomp_remaining ? TDEFL_NO_FLUSH + : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) { + result = MZ_TRUE; + break; + } else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); + pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header( + pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, + comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir( + pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, + mz_zip_archive *pSource_zip, + mz_uint file_index) { + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == + (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > + 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, + pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, + num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = + n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == + (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)MZ_MAX(sizeof(mz_uint32) * 4, + MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, + comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, + n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, + sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + // cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, + local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back( + pZip, &pState->m_central_dir, + pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || + ((pZip->m_archive_size + pState->m_central_dir.m_size + + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, + pState->m_central_dir.m_p, + (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, + pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, + sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, + size_t *pSize) { + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place( + const char *pZip_filename, const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags) { + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || + ((comment_size) && (!pComment)) || + ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } else { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + level_and_flags | + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = + mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, + pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid + // central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, + const char *pArchive_name, + size_t *pSize, mz_uint flags) { + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + flags | + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, + flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to +*/ diff --git a/ports/wapy/cmod/common/_zipfile/mod_zipfile.c b/ports/wapy/cmod/common/_zipfile/mod_zipfile.c new file mode 100644 index 000000000..e58ca51ec --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/mod_zipfile.c @@ -0,0 +1,330 @@ +/* http://github.com/pmp-p target pym:/data/cross/wapy/cmod/common/_zipfile.pym */ +/* + _zipfile.c AUTO-GENERATED by /data/cross/wapy/wapy-lib/modgen/__main__.py +*/ + +// ======= STATIC HEADER ======== + +#include +#include +#include // for free() + +#include "py/obj.h" +#include "py/runtime.h" + +#ifndef STATIC +#define STATIC static +#endif + + +#define None mp_const_none +#define bytes(cstr) PyBytes_FromString(cstr) +#define PyMethodDef const mp_map_elem_t +#define PyModuleDef const mp_obj_module_t + +#define mp_obj_get_double mp_obj_get_float +#define mp_obj_new_int_from_ptr mp_obj_new_int_from_ull + +#define mp_obj_new_int_from_unsigned_long mp_obj_new_int_from_uint +#define unsigned_long unsigned long + + + + + +// reuse embed exports +extern void print(mp_obj_t str); +extern void null_pointer_exception(void); +extern mp_obj_t PyBytes_FromString(char *string); +extern const char *nullbytes; + +// =========== embedded header from .pym ============ + +#include "zip.h" + +// end of embedded header +// ============================================================= + +typedef struct __zipfile_ZipFile_obj_t { + mp_obj_base_t base; + uint8_t hash; + void * zbuf; + size_t zbuf_size; + struct zip_t * zip; + char * path; +} _zipfile_ZipFile_obj_t; + + + +const mp_obj_type_t _zipfile_ZipFile_type; //forward decl + +mp_obj_t +_zipfile_ZipFile_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, 0, 0, true); + + _zipfile_ZipFile_obj_t *self = m_new_obj(_zipfile_ZipFile_obj_t); + + self->base.type = &_zipfile_ZipFile_type; + +// self->hash = object_id++; +// printf("Object serial #%d\n", self->hash ); + + self->hash = 0; + self->zbuf = NULL; + self->zbuf_size = 0; + self->zip = None; + self->path = NULL; + + return MP_OBJ_FROM_PTR(self); +} + + +void +_zipfile_ZipFile_print( const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind ) { + // get a ptr to the C-struct of the object + _zipfile_ZipFile_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // print the number + mp_printf (print, "<_zipfile.ZipFile at 0x%p>", self); +} + + +// Begin : class ZipFile: + + + +STATIC mp_obj_t // void -> void +_zipfile_ZipFile_open(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // def open(name: const_char_p="")->void: + // ('const_char_p', '""') => const char * = "" + + const char *name; // ('const char *', 'name', '""') + if (argc>1) { name = mp_obj_str_get_str(argv[1]); } else { name = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + zip_entry_open(self->zip, name); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_open_obj, 0, 2, _zipfile_ZipFile_open); + + + +STATIC mp_obj_t // bytes -> bytes +_zipfile_ZipFile_read(size_t argc, const mp_obj_t *argv) { + mp_obj_t __preturn__; + char * __creturn__ = (char *)nullbytes; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + + // ------- method body -------- + zip_entry_read(self->zip, &self->zbuf, &self->zbuf_size); + zip_entry_close(self->zip); + { //try: + if (self->zbuf) { + __creturn__ = (char *)self->zbuf; + } + { //finally: + __preturn__ = PyBytes_FromString(__creturn__); + free(self->zbuf); + } + } +return __preturn__; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_read_obj, 0, 1, _zipfile_ZipFile_read); + + + +STATIC mp_obj_t // void -> void +_zipfile_ZipFile_init(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // def init(path : const_char_p="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *path; // ('const char *', 'path', '""') + if (argc>1) { path = mp_obj_str_get_str(argv[1]); } else { path = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + self->path = strdup(path); + self->hash = strlen(path); + printf("ZIPFILE[%d;%s]self\n", self->hash, self->path); + self->zip = zip_open(self->path, 0, 'r'); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_init_obj, 0, 2, _zipfile_ZipFile_init); + + + +STATIC mp_obj_t // bytes -> bytes +_zipfile_ZipFile_filename(size_t argc, const mp_obj_t *argv) { +// opt: no finally bytes slot + char * __creturn__ = (char *)nullbytes; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + + // ------- method body (try/finally) ----- + { __creturn__ = (char *)self->path; goto lreturn__; }; +lreturn__: return PyBytes_FromString(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_filename_obj, 0, 1, _zipfile_ZipFile_filename); + + + +STATIC mp_obj_t // void -> void +_zipfile_ZipFile_close(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + + // ------- method body (try/finally) ----- + if (self->path) { + zip_close(self->zip); + free(self->path); + } +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_close_obj, 0, 1, _zipfile_ZipFile_close); + + + +STATIC mp_obj_t // int -> int +_zipfile_ZipFile_pouet(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // TODO: async : resume with go after last yield + + // ------- method body (try/finally) ----- + if (1) { + printf(" pouet arg0 %zu\n", argc ); + { __creturn__ = (long)42; goto lreturn__; }; + } +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_pouet_obj, 0, 1, _zipfile_ZipFile_pouet); + + + +STATIC mp_obj_t // int -> int +_zipfile_ZipFile_pouet2(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + _zipfile_ZipFile_obj_t *self = (_zipfile_ZipFile_obj_t *)MP_OBJ_TO_PTR(argv[0]); + (void)self; + + // TODO: async : resume with go after last yield + + // ------- method body (try/finally) ----- + if (1) { + if (2) { + { __creturn__ = (long)43; goto lreturn__; }; + } + } +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_zipfile_ZipFile_pouet2_obj, 0, 1, _zipfile_ZipFile_pouet2); + + + +// ++++++++ class ZipFile interface +++++++ + + + +STATIC const mp_map_elem_t _zipfile_ZipFile_dict_table[] = { + {MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&_zipfile_ZipFile_open_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_read), (mp_obj_t)&_zipfile_ZipFile_read_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&_zipfile_ZipFile_init_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_filename), (mp_obj_t)&_zipfile_ZipFile_filename_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&_zipfile_ZipFile_close_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_pouet), (mp_obj_t)&_zipfile_ZipFile_pouet_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_pouet2), (mp_obj_t)&_zipfile_ZipFile_pouet2_obj }, + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(_zipfile_ZipFile_dict, _zipfile_ZipFile_dict_table); + + + +const mp_obj_type_t _zipfile_ZipFile_type = { + + // "inherit" the type "type" + { &mp_type_type }, + + // give it a name + .name = MP_QSTR_ZipFile, + + // give it a print-function + .print = _zipfile_ZipFile_print, + + // give it a constructor + .make_new = _zipfile_ZipFile_make_new, + + // and its locals members + .locals_dict = (mp_obj_dict_t*)&_zipfile_ZipFile_dict, +}; + +// End: **************** class ZipFile ************ + + + +// global module dict : + + + +STATIC const mp_map_elem_t _zipfile_dict_table[] = { +// builtins + {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR__zipfile) }, + {MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_OBJ_NEW_QSTR(MP_QSTR_flashrom) }, + +// extensions + + +// Classes : + +// ZipFile class + {MP_OBJ_NEW_QSTR(MP_QSTR_ZipFile), (mp_obj_t)&_zipfile_ZipFile_type }, + + +// __main__ + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(_zipfile_dict, _zipfile_dict_table); + + + +//const mp_obj_module_t STATIC +PyModuleDef module__zipfile = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&_zipfile_dict, +}; + +// Register the module to make it available +MP_REGISTER_MODULE(MP_QSTR__zipfile, module__zipfile, MODULE__ZIPFILE_ENABLED); + diff --git a/ports/wapy/cmod/common/_zipfile/zip.c b/ports/wapy/cmod/common/_zipfile/zip.c new file mode 100644 index 000000000..3b2821e6a --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/zip.c @@ -0,0 +1,959 @@ +/* + * 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 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. + */ +#define __STDC_WANT_LIB_EXT1__ 1 + +#include +#include +#include + +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +/* Win32, DOS, MSVC, MSVS */ +#include + +#define MKDIR(DIRNAME) _mkdir(DIRNAME) +#define STRCLONE(STR) ((STR) ? _strdup(STR) : NULL) +#define HAS_DEVICE(P) \ + ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && \ + (P)[1] == ':') +#define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0) + +#else + +#include // needed for symlink() on BSD +int symlink(const char *target, const char *linkpath); // needed on Linux + +#define MKDIR(DIRNAME) mkdir(DIRNAME, 0755) +#define STRCLONE(STR) ((STR) ? strdup(STR) : NULL) + +#endif + +#include "miniz.h" +#include "zip.h" + +#ifndef HAS_DEVICE +#define HAS_DEVICE(P) 0 +#endif + +#ifndef FILESYSTEM_PREFIX_LEN +#define FILESYSTEM_PREFIX_LEN(P) 0 +#endif + +#ifndef ISSLASH +#define ISSLASH(C) ((C) == '/' || (C) == '\\') +#endif + +#define CLEANUP(ptr) \ + do { \ + if (ptr) { \ + free((void *)ptr); \ + ptr = NULL; \ + } \ + } while (0) + +static const char *base_name(const char *name) { + char const *p; + char const *base = name += FILESYSTEM_PREFIX_LEN(name); + int all_slashes = 1; + + for (p = name; *p; p++) { + if (ISSLASH(*p)) + base = p + 1; + else + all_slashes = 0; + } + + /* If NAME is all slashes, arrange to return `/'. */ + if (*base == '\0' && ISSLASH(*name) && all_slashes) + --base; + + return base; +} + +static int mkpath(char *path) { + char *p; + char npath[MAX_PATH + 1]; + int len = 0; + int has_device = HAS_DEVICE(path); + + memset(npath, 0, MAX_PATH + 1); + if (has_device) { + // only on windows + npath[0] = path[0]; + npath[1] = path[1]; + len = 2; + } + for (p = path + len; *p && len < MAX_PATH; p++) { + if (ISSLASH(*p) && ((!has_device && len > 0) || (has_device && len > 2))) { +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if ('\\' == *p) { + *p = '/'; + } +#endif + + if (MKDIR(npath) == -1) { + if (errno != EEXIST) { + return -1; + } + } + } + npath[len++] = *p; + } + + return 0; +} + +static char *strrpl(const char *str, size_t n, char oldchar, char newchar) { + char c; + size_t i; + char *rpl = (char *)calloc((1 + n), sizeof(char)); + char *begin = rpl; + if (!rpl) { + return NULL; + } + + for (i = 0; (i < n) && (c = *str++); ++i) { + if (c == oldchar) { + c = newchar; + } + *rpl++ = c; + } + + return begin; +} + +struct zip_entry_t { + int index; + char *name; + mz_uint64 uncomp_size; + mz_uint64 comp_size; + mz_uint32 uncomp_crc32; + mz_uint64 offset; + mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint64 header_offset; + mz_uint16 method; + mz_zip_writer_add_state state; + tdefl_compressor comp; + mz_uint32 external_attr; + time_t m_time; +}; + +struct zip_t { + mz_zip_archive archive; + mz_uint level; + struct zip_entry_t entry; +}; + +struct zip_t *zip_open(const char *zipname, int level, char mode) { + struct zip_t *zip = NULL; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + goto cleanup; + } + + if (level < 0) + level = MZ_DEFAULT_LEVEL; + if ((level & 0xF) > MZ_UBER_COMPRESSION) { + // Wrong compression level + goto cleanup; + } + + zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t)); + if (!zip) + goto cleanup; + + zip->level = (mz_uint)level; + switch (mode) { + case 'w': + // Create a new archive. + if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) { + // Cannot initialize zip_archive writer + goto cleanup; + } + break; + + case 'r': + case 'a': + if (!mz_zip_reader_init_file( + &(zip->archive), zipname, + zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) { + // An archive file does not exist or cannot initialize + // zip_archive reader + goto cleanup; + } + if (mode == 'a' && + !mz_zip_writer_init_from_reader(&(zip->archive), zipname)) { + mz_zip_reader_end(&(zip->archive)); + goto cleanup; + } + break; + + default: + goto cleanup; + } + + return zip; + +cleanup: + CLEANUP(zip); + return NULL; +} + +void zip_close(struct zip_t *zip) { + if (zip) { + // Always finalize, even if adding failed for some reason, so we have a + // valid central directory. + mz_zip_writer_finalize_archive(&(zip->archive)); + + mz_zip_writer_end(&(zip->archive)); + mz_zip_reader_end(&(zip->archive)); + + CLEANUP(zip); + } +} + +int zip_is64(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + if (!zip->archive.m_pState) { + // zip state is not initialized + return -1; + } + + return (int)zip->archive.m_pState->m_zip64; +} + +int zip_entry_open(struct zip_t *zip, const char *entryname) { + size_t entrylen = 0; + mz_zip_archive *pzip = NULL; + mz_uint num_alignment_padding_bytes, level; + mz_zip_archive_file_stat stats; + + if (!zip || !entryname) { + return -1; + } + + entrylen = strlen(entryname); + if (entrylen < 1) { + return -1; + } + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + zip->entry.name = strrpl(entryname, entrylen, '\\', '/'); + if (!zip->entry.name) { + // Cannot parse zip entry name + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + zip->entry.index = + mz_zip_reader_locate_file(pzip, zip->entry.name, NULL, 0); + if (zip->entry.index < 0) { + goto cleanup; + } + + if (!mz_zip_reader_file_stat(pzip, (mz_uint)zip->entry.index, &stats)) { + goto cleanup; + } + + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + zip->entry.external_attr = stats.m_external_attr; + zip->entry.m_time = stats.m_time; + + return 0; + } + + zip->entry.index = (int)zip->archive.m_total_files; + zip->entry.comp_size = 0; + zip->entry.uncomp_size = 0; + zip->entry.uncomp_crc32 = MZ_CRC32_INIT; + zip->entry.offset = zip->archive.m_archive_size; + zip->entry.header_offset = zip->archive.m_archive_size; + memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8)); + zip->entry.method = 0; + + // UNIX or APPLE +#if MZ_PLATFORM == 3 || MZ_PLATFORM == 19 + // regular file with rw-r--r-- persmissions + zip->entry.external_attr = (mz_uint32)(0100644) << 16; +#else + zip->entry.external_attr = 0; +#endif + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pzip); + + if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) { + // Wrong zip mode + goto cleanup; + } + if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) { + // Wrong zip compression level + goto cleanup; + } + // no zip64 support yet + if ((pzip->m_total_files == 0xFFFF) || + ((pzip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + entrylen) > 0xFFFFFFFF)) { + // No zip64 support yet + goto cleanup; + } + if (!mz_zip_writer_write_zeros(pzip, zip->entry.offset, + num_alignment_padding_bytes + + sizeof(zip->entry.header))) { + // Cannot memset zip entry header + goto cleanup; + } + + zip->entry.header_offset += num_alignment_padding_bytes; + if (pzip->m_file_offset_alignment) { + MZ_ASSERT( + (zip->entry.header_offset & (pzip->m_file_offset_alignment - 1)) == 0); + } + zip->entry.offset += num_alignment_padding_bytes + sizeof(zip->entry.header); + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name, + entrylen) != entrylen) { + // Cannot write data to zip entry + goto cleanup; + } + + zip->entry.offset += entrylen; + level = zip->level & 0xF; + if (level) { + zip->entry.state.m_pZip = pzip; + zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset; + zip->entry.state.m_comp_size = 0; + + if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback, + &(zip->entry.state), + (int)tdefl_create_comp_flags_from_zip_params( + (int)level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) { + // Cannot initialize the zip compressor + goto cleanup; + } + } + + zip->entry.m_time = time(NULL); + + return 0; + +cleanup: + CLEANUP(zip->entry.name); + return -1; +} + +int zip_entry_openbyindex(struct zip_t *zip, int index) { + mz_zip_archive *pZip = NULL; + mz_zip_archive_file_stat stats; + mz_uint namelen; + const mz_uint8 *pHeader; + const char *pFilename; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pZip = &(zip->archive); + if (pZip->m_zip_mode != MZ_ZIP_MODE_READING) { + // open by index requires readonly mode + return -1; + } + + if (index < 0 || (mz_uint)index >= pZip->m_total_files) { + // index out of range + return -1; + } + + if (!(pHeader = &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, + mz_uint32, index)))) { + // cannot find header in central directory + return -1; + } + + namelen = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + zip->entry.name = strrpl(pFilename, namelen, '\\', '/'); + if (!zip->entry.name) { + // local entry name is NULL + return -1; + } + + if (!mz_zip_reader_file_stat(pZip, (mz_uint)index, &stats)) { + return -1; + } + + zip->entry.index = index; + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + zip->entry.external_attr = stats.m_external_attr; + zip->entry.m_time = stats.m_time; + + return 0; +} + +int zip_entry_close(struct zip_t *zip) { + mz_zip_archive *pzip = NULL; + mz_uint level; + tdefl_status done; + mz_uint16 entrylen; + mz_uint16 dos_time, dos_date; + int status = -1; + + if (!zip) { + // zip_t handler is not initialized + goto cleanup; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + status = 0; + goto cleanup; + } + + level = zip->level & 0xF; + if (level) { + done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH); + if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) { + // Cannot flush compressed buffer + goto cleanup; + } + zip->entry.comp_size = zip->entry.state.m_comp_size; + zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs; + zip->entry.method = MZ_DEFLATED; + } + + entrylen = (mz_uint16)strlen(zip->entry.name); + // no zip64 support yet + if ((zip->entry.comp_size > 0xFFFFFFFF) || (zip->entry.offset > 0xFFFFFFFF)) { + // No zip64 support, yet + goto cleanup; + } + + mz_zip_time_t_to_dos_time(zip->entry.m_time, &dos_time, &dos_date); + if (!mz_zip_writer_create_local_dir_header( + pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size, + zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, + dos_time, dos_date)) { + // Cannot create zip entry header + goto cleanup; + } + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset, + zip->entry.header, + sizeof(zip->entry.header)) != sizeof(zip->entry.header)) { + // Cannot write zip entry header + goto cleanup; + } + + if (!mz_zip_writer_add_to_central_dir( + pzip, zip->entry.name, entrylen, NULL, 0, "", 0, + zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32, + zip->entry.method, 0, dos_time, dos_date, zip->entry.header_offset, + zip->entry.external_attr)) { + // Cannot write to zip central dir + goto cleanup; + } + + pzip->m_total_files++; + pzip->m_archive_size = zip->entry.offset; + status = 0; + +cleanup: + if (zip) { + zip->entry.m_time = 0; + CLEANUP(zip->entry.name); + } + return status; +} + +const char *zip_entry_name(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return NULL; + } + + return zip->entry.name; +} + +int zip_entry_index(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + return zip->entry.index; +} + +int zip_entry_isdir(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + if (zip->entry.index < 0) { + // zip entry is not opened + return -1; + } + + return (int)mz_zip_reader_is_file_a_directory(&zip->archive, + (mz_uint)zip->entry.index); +} + +unsigned long long zip_entry_size(struct zip_t *zip) { + return zip ? zip->entry.uncomp_size : 0; +} + +unsigned int zip_entry_crc32(struct zip_t *zip) { + return zip ? zip->entry.uncomp_crc32 : 0; +} + +int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) { + mz_uint level; + mz_zip_archive *pzip = NULL; + tdefl_status status; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (buf && bufsize > 0) { + zip->entry.uncomp_size += bufsize; + zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32( + zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize); + + level = zip->level & 0xF; + if (!level) { + if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf, + bufsize) != bufsize)) { + // Cannot write buffer + return -1; + } + zip->entry.offset += bufsize; + zip->entry.comp_size += bufsize; + } else { + status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize, + TDEFL_NO_FLUSH); + if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) { + // Cannot compress buffer + return -1; + } + } + } + + return 0; +} + +int zip_entry_fwrite(struct zip_t *zip, const char *filename) { + int status = 0; + size_t n = 0; + FILE *stream = NULL; + mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE]; + struct MZ_FILE_STAT_STRUCT file_stat; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + memset(buf, 0, MZ_ZIP_MAX_IO_BUF_SIZE); + memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT)); + if (MZ_FILE_STAT(filename, &file_stat) != 0) { + // problem getting information - check errno + return -1; + } + + if ((file_stat.st_mode & 0200) == 0) { + // MS-DOS read-only attribute + zip->entry.external_attr |= 0x01; + } + zip->entry.external_attr |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16); + zip->entry.m_time = file_stat.st_mtime; + +#if defined(_MSC_VER) + if (fopen_s(&stream, filename, "rb")) +#else + if (!(stream = fopen(filename, "rb"))) +#endif + { + // Cannot open filename + return -1; + } + + while ((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) > + 0) { + if (zip_entry_write(zip, buf, n) < 0) { + status = -1; + break; + } + } + fclose(stream); + + return status; +} + +ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + size_t size = 0; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return -1; + } + + *buf = mz_zip_reader_extract_to_heap(pzip, idx, &size, 0); + if (*buf && bufsize) { + *bufsize = size; + } + return size; +} + +ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { + mz_zip_archive *pzip = NULL; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, (mz_uint)zip->entry.index, + buf, bufsize, 0, NULL, 0)) { + return -1; + } + + return (ssize_t)zip->entry.uncomp_size; +} + +int zip_entry_fread(struct zip_t *zip, const char *filename) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + mz_uint32 xattr = 0; + mz_zip_archive_file_stat info; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat)); + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return -1; + } + + if (!mz_zip_reader_extract_to_file(pzip, idx, filename, 0)) { + return -1; + } + +#if defined(_MSC_VER) +#else + if (!mz_zip_reader_file_stat(pzip, idx, &info)) { + // Cannot get information about zip archive; + return -1; + } + + xattr = (info.m_external_attr >> 16) & 0xFFFF; + if (xattr > 0) { + if (chmod(filename, (mode_t)xattr) < 0) { + return -1; + } + } +#endif + + return 0; +} + +int zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, unsigned long long offset, + const void *buf, size_t bufsize), + void *arg) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return -1; + } + + idx = (mz_uint)zip->entry.index; + return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0)) + ? 0 + : -1; +} + +int zip_total_entries(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + return (int)zip->archive.m_total_files; +} + +int zip_create(const char *zipname, const char *filenames[], size_t len) { + int status = 0; + size_t i; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_uint32 ext_attributes = 0; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + return -1; + } + + // Create a new archive. + if (!memset(&(zip_archive), 0, sizeof(zip_archive))) { + // Cannot memset zip archive + return -1; + } + + if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive writer + return -1; + } + + memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT)); + + for (i = 0; i < len; ++i) { + const char *name = filenames[i]; + if (!name) { + status = -1; + break; + } + + if (MZ_FILE_STAT(name, &file_stat) != 0) { + // problem getting information - check errno + status = -1; + break; + } + + if ((file_stat.st_mode & 0200) == 0) { + // MS-DOS read-only attribute + ext_attributes |= 0x01; + } + ext_attributes |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16); + + if (!mz_zip_writer_add_file(&zip_archive, base_name(name), name, "", 0, + ZIP_DEFAULT_COMPRESSION_LEVEL, + ext_attributes)) { + // Cannot add file to zip_archive + status = -1; + break; + } + } + + mz_zip_writer_finalize_archive(&zip_archive); + mz_zip_writer_end(&zip_archive); + return status; +} + +int zip_extract(const char *zipname, const char *dir, + int (*on_extract)(const char *filename, void *arg), void *arg) { + int status = -1; + mz_uint i, n; + char path[MAX_PATH + 1]; + char symlink_to[MAX_PATH + 1]; + mz_zip_archive zip_archive; + mz_zip_archive_file_stat info; + size_t dirlen = 0; + mz_uint32 xattr = 0; + + memset(path, 0, sizeof(path)); + memset(symlink_to, 0, sizeof(symlink_to)); + if (!memset(&(zip_archive), 0, sizeof(zip_archive))) { + // Cannot memset zip archive + return -1; + } + + if (!zipname || !dir) { + // Cannot parse zip archive name + return -1; + } + + dirlen = strlen(dir); + if (dirlen + 1 > MAX_PATH) { + return -1; + } + + // Now try to open the archive. + if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive reader + return -1; + } + + memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat)); + +#if defined(_MSC_VER) + strcpy_s(path, MAX_PATH, dir); +#else + strcpy(path, dir); +#endif + + if (!ISSLASH(path[dirlen - 1])) { +#if defined(_WIN32) || defined(__WIN32__) + path[dirlen] = '\\'; +#else + path[dirlen] = '/'; +#endif + ++dirlen; + } + + // Get and print information about each file in the archive. + n = mz_zip_reader_get_num_files(&zip_archive); + for (i = 0; i < n; ++i) { + if (!mz_zip_reader_file_stat(&zip_archive, i, &info)) { + // Cannot get information about zip archive; + goto out; + } +#if defined(_MSC_VER) + strncpy_s(&path[dirlen], MAX_PATH - dirlen, info.m_filename, + MAX_PATH - dirlen); +#else + strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen); +#endif + if (mkpath(path) < 0) { + // Cannot make a path + goto out; + } + + if ((((info.m_version_made_by >> 8) == 3) || + ((info.m_version_made_by >> 8) == + 19)) // if zip is produced on Unix or macOS (3 and 19 from + // section 4.4.2.2 of zip standard) + && info.m_external_attr & + (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 + // is directory) +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if (info.m_uncomp_size > MAX_PATH || + !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, + MAX_PATH, 0, NULL, 0)) { + goto out; + } + symlink_to[info.m_uncomp_size] = '\0'; + if (symlink(symlink_to, path) != 0) { + goto out; + } +#endif + } else { + if (!mz_zip_reader_is_file_a_directory(&zip_archive, i)) { + if (!mz_zip_reader_extract_to_file(&zip_archive, i, path, 0)) { + // Cannot extract zip archive to file + goto out; + } + } + +#if defined(_MSC_VER) +#else + xattr = (info.m_external_attr >> 16) & 0xFFFF; + if (xattr > 0) { + if (chmod(path, (mode_t)xattr) < 0) { + goto out; + } + } +#endif + } + + if (on_extract) { + if (on_extract(path, arg) < 0) { + goto out; + } + } + } + status = 0; + +out: + // Close the archive, freeing any resources it was using + if (!mz_zip_reader_end(&zip_archive)) { + // Cannot end zip reader + status = -1; + } + + return status; +} diff --git a/ports/wapy/cmod/common/_zipfile/zip.h b/ports/wapy/cmod/common/_zipfile/zip.h new file mode 100644 index 000000000..cd3ab5cd0 --- /dev/null +++ b/ports/wapy/cmod/common/_zipfile/zip.h @@ -0,0 +1,322 @@ +/* + * 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 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. + */ + +#pragma once +#ifndef ZIP_H +#define ZIP_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(_SSIZE_T_DEFINED) && !defined(_SSIZE_T_DEFINED_) && \ + !defined(__DEFINED_ssize_t) && !defined(__ssize_t_defined) && \ + !defined(_SSIZE_T) && !defined(_SSIZE_T_) && !defined(_SSIZE_T_DECLARED) + +// 64-bit Windows is the only mainstream platform +// where sizeof(long) != sizeof(void*) +#ifdef _WIN64 +typedef long long ssize_t; /* byte count or error */ +#else +typedef long ssize_t; /* byte count or error */ +#endif + +#define _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED_ +#define __DEFINED_ssize_t +#define __ssize_t_defined +#define _SSIZE_T +#define _SSIZE_T_ +#define _SSIZE_T_DECLARED + +#endif + +#ifndef MAX_PATH +#define MAX_PATH 32767 /* # chars in a path name including NULL */ +#endif + +/** + * @mainpage + * + * Documenation for @ref zip. + */ + +/** + * @addtogroup zip + * @{ + */ + +/** + * Default zip compression level. + */ + +#define ZIP_DEFAULT_COMPRESSION_LEVEL 6 + +/** + * @struct zip_t + * + * This data structure is used throughout the library to represent zip archive - + * forward declaration. + */ +struct zip_t; + +/** + * Opens zip archive with compression level using the given mode. + * + * @param zipname zip archive file name. + * @param level compression level (0-9 are the standard zlib-style levels). + * @param mode file access mode. + * - 'r': opens a file for reading/extracting (the file must exists). + * - 'w': creates an empty file for writing. + * - 'a': appends to an existing archive. + * + * @return the zip archive handler or NULL on error + */ +extern struct zip_t *zip_open(const char *zipname, int level, char mode); + +/** + * Closes the zip archive, releases resources - always finalize. + * + * @param zip zip archive handler. + */ +extern void zip_close(struct zip_t *zip); + +/** + * Determines if the archive has a zip64 end of central directory headers. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ +extern int zip_is64(struct zip_t *zip); + +/** + * Opens an entry by name in the zip archive. + * + * For zip archive opened in 'w' or 'a' mode the function will append + * a new entry. In readonly mode the function tries to locate the entry + * in global dictionary. + * + * @param zip zip archive handler. + * @param entryname an entry name in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_open(struct zip_t *zip, const char *entryname); + +/** + * Opens a new entry by index in the zip archive. + * + * This function is only valid if zip archive was opened in 'r' (readonly) mode. + * + * @param zip zip archive handler. + * @param index index in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_openbyindex(struct zip_t *zip, int index); + +/** + * Closes a zip entry, flushes buffer and releases resources. + * + * @param zip zip archive handler. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_close(struct zip_t *zip); + +/** + * Returns a local name of the current zip entry. + * + * The main difference between user's entry name and local entry name + * is optional relative path. + * Following .ZIP File Format Specification - the path stored MUST not contain + * a drive or device letter, or a leading slash. + * All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' + * for compatibility with Amiga and UNIX file systems etc. + * + * @param zip: zip archive handler. + * + * @return the pointer to the current zip entry name, or NULL on error. + */ +extern const char *zip_entry_name(struct zip_t *zip); + +/** + * Returns an index of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the index on success, negative number (< 0) on error. + */ +extern int zip_entry_index(struct zip_t *zip); + +/** + * Determines if the current zip entry is a directory entry. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ +extern int zip_entry_isdir(struct zip_t *zip); + +/** + * Returns an uncompressed size of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the uncompressed size in bytes. + */ +extern unsigned long long zip_entry_size(struct zip_t *zip); + +/** + * Returns CRC-32 checksum of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the CRC-32 checksum. + */ +extern unsigned int zip_entry_crc32(struct zip_t *zip); + +/** + * Compresses an input buffer for the current zip entry. + * + * @param zip zip archive handler. + * @param buf input buffer. + * @param bufsize input buffer size (in bytes). + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize); + +/** + * Compresses a file for the current zip entry. + * + * @param zip zip archive handler. + * @param filename input file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_fwrite(struct zip_t *zip, const char *filename); + +/** + * Extracts the current zip entry into output buffer. + * + * The function allocates sufficient memory for a output buffer. + * + * @param zip zip archive handler. + * @param buf output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note remember to release memory allocated for a output buffer. + * for large entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error. + */ +extern ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize); + +/** + * Extracts the current zip entry into a memory buffer using no memory + * allocation. + * + * @param zip zip archive handler. + * @param buf preallocated output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note ensure supplied output buffer is large enough. + * zip_entry_size function (returns uncompressed size for the current + * entry) can be handy to estimate how big buffer is needed. for large + * entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error (e.g. bufsize is not large enough). + */ +extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, + size_t bufsize); + +/** + * Extracts the current zip entry into output file. + * + * @param zip zip archive handler. + * @param filename output file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_entry_fread(struct zip_t *zip, const char *filename); + +/** + * Extracts the current zip entry using a callback function (on_extract). + * + * @param zip zip archive handler. + * @param on_extract callback function. + * @param arg opaque pointer (optional argument, which you can pass to the + * on_extract callback) + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int +zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, unsigned long long offset, + const void *data, size_t size), + void *arg); + +/** + * Returns the number of all entries (files and directories) in the zip archive. + * + * @param zip zip archive handler. + * + * @return the return code - the number of entries on success, negative number + * (< 0) on error. + */ +extern int zip_total_entries(struct zip_t *zip); + +/** + * Creates a new archive and puts files into a single zip archive. + * + * @param zipname zip archive file. + * @param filenames input files. + * @param len: number of input files. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_create(const char *zipname, const char *filenames[], size_t len); + +/** + * Extracts a zip archive file into directory. + * + * If on_extract_entry is not NULL, the callback will be called after + * successfully extracted each zip entry. + * Returning a negative value from the callback will cause abort and return an + * error. The last argument (void *arg) is optional, which you can use to pass + * data to the on_extract_entry callback. + * + * @param zipname zip archive file. + * @param dir output directory. + * @param on_extract_entry on extract callback. + * @param arg opaque pointer. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern int zip_extract(const char *zipname, const char *dir, + int (*on_extract_entry)(const char *filename, void *arg), + void *arg); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/wapy/cmod/common/embed.pym b/ports/wapy/cmod/common/embed.pym new file mode 100644 index 000000000..12a8b3fb0 --- /dev/null +++ b/ports/wapy/cmod/common/embed.pym @@ -0,0 +1,364 @@ +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// + +#ifndef wa_clock_gettime + #define wa_clock_gettime(clockid, timespec) clock_gettime(clockid, timespec) + #define wa_gettimeofday(timeval, tmz) gettimeofday(timeval, tmz) + static struct timespec t_timespec; + static struct timeval t_timeval; +#else + extern struct timespec t_timespec; + extern struct timeval t_timeval; +#endif + + +#if defined(__EMSCRIPTEN__) || defined(__WASI__) + #if defined(__WASI__) + #error "wasi cannot use emsdk" + #else + #define fd_logger stderr + #endif + + #define WAPY_VALUE (1) + extern int VMFLAGS_IF; + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#else + #define fd_logger stderr + #define WAPY_VALUE (0) + int VMFLAGS_IF = 0; + int show_os_loop(int state) {return 0;} + int state_os_loop(int state) {return 0;} + void emscripten_sleep(int t){} +#endif + +#include "emscripten.h" + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include +#include + + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +//#include +//extern uintptr_t SDL_embed(uintptr_t *ptr); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +# this one is used on MCU to run asyncio loop from repl loop idle state +# TODO: fix NO_NLR mode +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + +# code trace +# ========================================== +qstr trace_prev_file = 1/*MP_QSTR_*/; +qstr trace_file = 1/*MP_QSTR_*/; + +size_t trace_prev_line; +size_t trace_line; + +int trace_on; + +# corepy +# ========================================== + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(fd_logger,"122:FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +# finalizers +# ========================================== + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max ; + cstr[0]=0; + +def run(runstr : const_char_p = "[]"): + # os interfacing, this is not really running except you could have eval() on the other side + # this is actually for passing json / cbor etc ... + + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(stderr, "182:buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + } + + + +def disable_irq(): + VMFLAGS_IF--; + +def enable_irq(): + VMFLAGS_IF++; + +def FLAGS_IF() -> _int_from_uint: + return VMFLAGS_IF + +def WAPY() -> _int_from_uint: + return WAPY_VALUE + +def os_print( data : const_char_p = "{}" ) -> void : + fprintf( stdout , "%s\n" , data ); + +def os_write( data : const_char_p = "{}" ) -> void : + fprintf( stdout , "%s" , data ); + +def os_stderr( data : const_char_p = "" ) -> void : + fprintf(stderr, "%s\n", data ); + +def log( data : const_char_p = "" ) -> void : + fprintf(fd_logger, "py: %s\n", data ); + +def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + return mod_globals; + +def os_state_loop(state:int = 0)->int: + return mp_obj_new_int( state_os_loop(state) ); + +def os_showloop()->void : + fprintf(stderr, "will show begin/end for os loop\n"); + show_os_loop(1); + +def os_hideloop()->void : + fprintf(stderr, "will hide begin/end for os loop\n"); + show_os_loop(0); + +def os_hook() ->void: + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass ; + printf("coro task started\n"); + coropass(); + } + } + +// https://www.python.org/dev/peps/pep-0564/ +// https://vstinner.github.io/python37-pep-564-nanoseconds.html + +def time_ns() -> int: + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000000000 + t_timespec.tv_nsec ; + return mp_obj_new_int_from_ull(ul) + +// upy +def time_ms() -> int: + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000 ; + return mp_obj_new_int_from_ull(ul); + + +def ticks_add(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks + delta ); + +def ticks_diff(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks - delta ); + + +def sleep_ms(ms : int =0) -> void : + //emscripten_sleep_with_yield( ms ); + emscripten_sleep(ms); + +def sleep(s : float =0) -> void : + emscripten_sleep( (int)(s*1000) ); + + +def ticks_period() -> int: + return mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); + + +//def os_read() -> bytes: +// return bytes( repl_line ); + +// TODO: remove after tests + + +def os_read_useless() -> bytes: + // simple read string + + static char buf[256]; + //fputs(p, stdout); + char *s = fgets(buf, sizeof(buf), stdin); + if (!s) { + //return mp_obj_new_int(0); + buf[0]=0; + fprintf(fd_logger,"embed.os_read EOF\n" ); + } else { + int l = strlen(buf); + if (buf[l - 1] == '\n') { + if ( (l>1) && (buf[l - 2] == '\r') ) { + buf[l - 2] = 0; + } else { + buf[l - 1] = 0; + } + } else { + l++; + } + fprintf(stderr,"embed.os_read [%s]\n", buf ); + } + return bytes(buf); + # py comment #1 + + +def echosum1(num : int=0) -> int: + return MP_OBJ_NEW_SMALL_INT(num+1); + +# py comment #2 + +def callsome(fnptr : void_p=npe) -> void: + void (*fn)() = fnptr; + (*fn)(); + +def callpy(fn: const_char_p ="") -> void: + fprintf(fd_logger, "embed.callpy[%s]\n", fn ) + pycore(fn) + +def hash_djb2(cstr : const_char_p = "") -> _int_from_unsigned_long: + unsigned long hash = 5381 + int c + while ((c = *cstr++)): + hash = ((hash << 5) + hash) + c + return hash % 0xFFFFFFFF + +def show_trace() -> int: + trace_on = 1 + fprintf(fd_logger, "TRACE[%s:%zu -> %s:%zu]\n", qstr_str(trace_prev_file), trace_prev_line, qstr_str(trace_file), trace_line) + return trace_prev_line-1 + + +def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + return ptrvalue; + +def set_ffpy(fn: void_p) -> void: + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn ; + +def set_ffpy_add(fn: void_p) -> void: + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn ; + + +def corepy(fn: const_char_p = "") -> _int_from_ptr: + fprintf(fd_logger, "embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy): + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + +def somecall(s:str='pouet'): + fprintf(fd_logger, "FPRINTF[%s]\n", mp_obj_str_get_str((char *)s) ); + print( (char *)s); + + + +// c comment #3 + diff --git a/ports/wapy/cmod/common/embed/micropython.mk b/ports/wapy/cmod/common/embed/micropython.mk new file mode 100644 index 000000000..b214da743 --- /dev/null +++ b/ports/wapy/cmod/common/embed/micropython.mk @@ -0,0 +1,8 @@ + +EMBED_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(wildcard $(EMBED_MOD_DIR)/*.c) + +# add module folder to include paths if needed +CFLAGS_USERMOD += -I$(EMBED_MOD_DIR) diff --git a/ports/wapy/cmod/common/embed/modembed.c b/ports/wapy/cmod/common/embed/modembed.c new file mode 100644 index 000000000..4cd5f3a1f --- /dev/null +++ b/ports/wapy/cmod/common/embed/modembed.c @@ -0,0 +1,944 @@ +/* http://github.com/pmp-p target pym:/data/cross/wapy/cmod/common/embed.pym */ +/* + embed.c AUTO-GENERATED by /data/cross/wapy/wapy-lib/modgen/__main__.py +*/ + +// ======= STATIC HEADER ======== + +#include +#include +#include // for free() + +#include "py/obj.h" +#include "py/runtime.h" + +#ifndef STATIC +#define STATIC static +#endif + + +#define None mp_const_none +#define bytes(cstr) PyBytes_FromString(cstr) +#define PyMethodDef const mp_map_elem_t +#define PyModuleDef const mp_obj_module_t + +#define mp_obj_get_double mp_obj_get_float +#define mp_obj_new_int_from_ptr mp_obj_new_int_from_ull + +#define mp_obj_new_int_from_unsigned_long mp_obj_new_int_from_uint +#define unsigned_long unsigned long + + + + + +// embed exports +void print(mp_obj_t str) { + mp_obj_print(str, PRINT_STR); + mp_obj_print(mp_obj_new_str_via_qstr("\n",1), PRINT_STR); +} + +void +null_pointer_exception(void) { + fprintf(stderr, "59: null pointer exception in function pointer call\n"); +} + +mp_obj_t +PyBytes_FromString(char *string){ + vstr_t vstr; + vstr_init_len(&vstr, strlen(string)); + strcpy(vstr.buf, string); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} + +const char *nullbytes = ""; +//static int orem_id = 0; + + +// =========== embedded header from .pym ============ + +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// + +#ifndef wa_clock_gettime + #define wa_clock_gettime(clockid, timespec) clock_gettime(clockid, timespec) + #define wa_gettimeofday(timeval, tmz) gettimeofday(timeval, tmz) + static struct timespec t_timespec; + static struct timeval t_timeval; +#else + extern struct timespec t_timespec; + extern struct timeval t_timeval; +#endif + + +#if defined(__EMSCRIPTEN__) || defined(__WASI__) + #if defined(__WASI__) + #error "wasi cannot use emsdk" + #else + #define fd_logger stderr + #endif + + #define WAPY_VALUE (1) + extern int VMFLAGS_IF; + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#else + #define fd_logger stderr + #define WAPY_VALUE (0) + int VMFLAGS_IF = 0; + int show_os_loop(int state) {return 0;} + int state_os_loop(int state) {return 0;} + void emscripten_sleep(int t){} +#endif + +#include "emscripten.h" + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include +#include + + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +//#include +//extern uintptr_t SDL_embed(uintptr_t *ptr); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +// this one is used on MCU to run asyncio loop from repl loop idle state + +// TODO: fix NO_NLR mode + +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + +// code trace + +// ========================================== + +qstr trace_prev_file = 1/*MP_QSTR_*/; +qstr trace_file = 1/*MP_QSTR_*/; + +size_t trace_prev_line; +size_t trace_line; + +int trace_on; + +// corepy + +// ========================================== + + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(fd_logger,"122:FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +// finalizers + +// ========================================== + + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +// end of embedded header +// ============================================================= + + + +STATIC mp_obj_t // void -> void +embed_set_io_buffer(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + // ('int', '0') => int = 0 + + int ptr; // ('int', 'ptr', '0') + if (argc>0) { ptr = mp_obj_get_int(argv[0]); } else { ptr = 0 ; } + + + int ptr_max; // ('int', 'ptr_max', '0') + if (argc>1) { ptr_max = mp_obj_get_int(argv[1]); } else { ptr_max = 0 ; } + + + // ------- method body (try/finally) ----- + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max; + cstr[0]=0; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_io_buffer_obj, 0, 2, embed_set_io_buffer); + + + +STATIC mp_obj_t // void -> void +embed_run(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def run(runstr : const_char_p = "[]"): + // ('const_char_p', '"[]"') => const char * = "[]" + + const char *runstr; // ('const char *', 'runstr', '"[]"') + if (argc>0) { runstr = mp_obj_str_get_str(argv[0]); } else { runstr = mp_obj_new_str_via_qstr("[]",2); } + + + // ------- method body (try/finally) ----- + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(stderr, "182:buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_run_obj, 0, 1, embed_run); + + + +STATIC mp_obj_t // void -> void +embed_disable_irq(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF--; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_disable_irq_obj, 0, 0, embed_disable_irq); + + + +STATIC mp_obj_t // void -> void +embed_enable_irq(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF++; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_enable_irq_obj, 0, 0, embed_enable_irq); + + + +STATIC mp_obj_t // _int_from_uint -> _int_from_uint +embed_FLAGS_IF(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_uint slot + uint __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (uint)VMFLAGS_IF; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_uint(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_FLAGS_IF_obj, 0, 0, embed_FLAGS_IF); + + + +STATIC mp_obj_t // _int_from_uint -> _int_from_uint +embed_WAPY(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_uint slot + uint __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (uint)WAPY_VALUE; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_uint(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_WAPY_obj, 0, 0, embed_WAPY); + + + +STATIC mp_obj_t // void -> void +embed_os_print(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_print( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s\n" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_print_obj, 0, 1, embed_os_print); + + + +STATIC mp_obj_t // void -> void +embed_os_write(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_write( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_write_obj, 0, 1, embed_os_write); + + + +STATIC mp_obj_t // void -> void +embed_os_stderr(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_stderr( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(stderr, "%s\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_stderr_obj, 0, 1, embed_os_stderr); + + + +STATIC mp_obj_t // void -> void +embed_log(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def log( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "py: %s\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_log_obj, 0, 1, embed_log); + + + +STATIC mp_obj_t // dict -> dict +embed_builtins_vars(size_t argc, const mp_obj_t *argv) { +// opt: no finally dict slot + mp_obj_dict_t * __creturn__; + // def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + // ('mp_obj_t', 'None') => mp_obj_t = None + + mp_obj_t module_obj; // ('mp_obj_t', 'module_obj', 'NULL') + if (argc>0) { module_obj = (mp_obj_t)argv[0]; } + else { module_obj = NULL ; } + + + // ------- method body (try/finally) ----- + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + { __creturn__ = (mp_obj_dict_t *)mod_globals; goto lreturn__; }; +lreturn__: return (mp_obj_t)__creturn__ ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_builtins_vars_obj, 0, 1, embed_builtins_vars); + + + +STATIC mp_obj_t // int -> int +embed_os_state_loop(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def os_state_loop(state:int = 0)->int: + // ('int', '0') => int = 0 + + int state; // ('int', 'state', '0') + if (argc>0) { state = mp_obj_get_int(argv[0]); } else { state = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int( state_os_loop(state) ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_state_loop_obj, 0, 1, embed_os_state_loop); + + + +STATIC mp_obj_t // void -> void +embed_os_showloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(stderr, "will show begin/end for os loop\n"); + show_os_loop(1); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_showloop_obj, 0, 0, embed_os_showloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hideloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(stderr, "will hide begin/end for os loop\n"); + show_os_loop(0); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hideloop_obj, 0, 0, embed_os_hideloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hook(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass; + printf("coro task started\n"); + coropass(); + }; + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hook_obj, 0, 0, embed_os_hook); + + + +STATIC mp_obj_t // int -> int +embed_time_ns(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000000000 + t_timespec.tv_nsec; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ns_obj, 0, 0, embed_time_ns); + + + +STATIC mp_obj_t // int -> int +embed_time_ms(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ms_obj, 0, 0, embed_time_ms); + + + +STATIC mp_obj_t // int -> int +embed_ticks_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_add(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks + delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_add_obj, 0, 2, embed_ticks_add); + + + +STATIC mp_obj_t // int -> int +embed_ticks_diff(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_diff(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks - delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_diff_obj, 0, 2, embed_ticks_diff); + + + +STATIC mp_obj_t // void -> void +embed_sleep_ms(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def sleep_ms(ms : int =0) -> void : + // ('int', '0') => int = 0 + + int ms; // ('int', 'ms', '0') + if (argc>0) { ms = mp_obj_get_int(argv[0]); } else { ms = 0 ; } + + + // ------- method body (try/finally) ----- + emscripten_sleep(ms); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_sleep_ms_obj, 0, 1, embed_sleep_ms); + + + +STATIC mp_obj_t // void -> void +embed_sleep(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def sleep(s : float =0) -> void : + // ('float', '0') => float = 0 + + double s; // ('double', 's', '0') + if (argc>0) { s = mp_obj_get_double(argv[0]); } else { s = 0 ; } + + + // ------- method body (try/finally) ----- + emscripten_sleep( (int)(s*1000) ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_sleep_obj, 0, 1, embed_sleep); + + + +STATIC mp_obj_t // int -> int +embed_ticks_period(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_period_obj, 0, 0, embed_ticks_period); + + + +STATIC mp_obj_t // bytes -> bytes +embed_os_read_useless(size_t argc, const mp_obj_t *argv) { +// opt: no finally bytes slot + char * __creturn__ = (char *)nullbytes; + + // ------- method body (try/finally) ----- + static char buf[256]; + char *s = fgets(buf, sizeof(buf), stdin); + if (!s) { + buf[0]=0; + fprintf(fd_logger,"embed.os_read EOF\n" ); + } else { + int l = strlen(buf); + if (buf[l - 1] == '\n') { + if ( (l>1) && (buf[l - 2] == '\r') ) { + buf[l - 2] = 0; + } else { + buf[l - 1] = 0; + }; + } else { + l++; + }; + fprintf(stderr,"embed.os_read [%s]\n", buf ); + }; + { __creturn__ = (char *)bytes(buf); goto lreturn__; }; +lreturn__: return PyBytes_FromString(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_read_useless_obj, 0, 0, embed_os_read_useless); + + + +STATIC mp_obj_t // int -> int +embed_echosum1(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def echosum1(num : int=0) -> int: + // ('int', '0') => int = 0 + + int num; // ('int', 'num', '0') + if (argc>0) { num = mp_obj_get_int(argv[0]); } else { num = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)MP_OBJ_NEW_SMALL_INT(num+1); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_echosum1_obj, 0, 1, embed_echosum1); + + + +STATIC mp_obj_t // void -> void +embed_callsome(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def callsome(fnptr : void_p=npe) -> void: + // ('void_p', 'npe') => void * = NULL + + void * fnptr; // ('void *', 'fnptr', 'NULL') + if (argc>0) { fnptr = (void *)argv[0]; } + else { fnptr = NULL ; } + + + // ------- method body (try/finally) ----- + void (*fn)() = fnptr; + (*fn)(); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_callsome_obj, 0, 1, embed_callsome); + + + +STATIC mp_obj_t // void -> void +embed_callpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def callpy(fn: const_char_p ="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.callpy[%s]\n", fn ); + pycore(fn); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_callpy_obj, 0, 1, embed_callpy); + + + +STATIC mp_obj_t // _int_from_unsigned_long -> _int_from_unsigned_long +embed_hash_djb2(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_unsigned_long slot + unsigned_long __creturn__ = 0; + // def hash_djb2(cstr : const_char_p = "") -> _int_from_unsigned_long: + // ('const_char_p', '""') => const char * = "" + + const char *cstr; // ('const char *', 'cstr', '""') + if (argc>0) { cstr = mp_obj_str_get_str(argv[0]); } else { cstr = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + unsigned long hash = 5381; + int c; + while ((c = *cstr++)) { + hash = ((hash << 5) + hash) + c; + } + { __creturn__ = (unsigned_long)hash % 0xFFFFFFFF; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_unsigned_long(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_hash_djb2_obj, 0, 1, embed_hash_djb2); + + + +STATIC mp_obj_t // int -> int +embed_show_trace(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + trace_on = 1; + fprintf(fd_logger, "TRACE[%s:%zu -> %s:%zu]\n", qstr_str(trace_prev_file), trace_prev_line, qstr_str(trace_file), trace_line); + { __creturn__ = (long)trace_prev_line-1; goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_show_trace_obj, 0, 0, embed_show_trace); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_address_of(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + // ('void_p', 'NULL') => void * = NULL + + void * ptr; // ('void *', 'ptr', 'NULL') + if (argc>0) { ptr = (void *)argv[0]; } + else { ptr = NULL ; } + + + // ------- method body (try/finally) ----- + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + { __creturn__ = (uintptr_t)ptrvalue; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_address_of_obj, 0, 1, embed_address_of); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_obj, 0, 1, embed_set_ffpy); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy_add(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_add_obj, 0, 1, embed_set_ffpy_add); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_corepy(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def corepy(fn: const_char_p = "") -> _int_from_ptr: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + } +return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_corepy_obj, 0, 1, embed_corepy); + + + +STATIC mp_obj_t // void -> void +embed_somecall(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def somecall(s:str='pouet'): + // ('str', "'pouet'") => str = 'pouet' + + const char *s; // ('str', 's', "'pouet'") + if (argc>0) { s = mp_obj_str_get_str(argv[0]); } else { s = mp_obj_new_str_via_qstr("pouet",5); } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "FPRINTF[%s]\n", mp_obj_str_get_str((char *)s) ); + print( (char *)s); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_somecall_obj, 0, 1, embed_somecall); + + + +// global module dict : + + + +STATIC const mp_map_elem_t embed_dict_table[] = { +// builtins + {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_embed) }, + {MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_OBJ_NEW_QSTR(MP_QSTR_flashrom) }, + +// extensions + {MP_OBJ_NEW_QSTR(MP_QSTR_on_del), MP_ROM_PTR(&mp_type_on_del) }, + + +// Classes : + + +// __main__ + {MP_OBJ_NEW_QSTR(MP_QSTR_set_io_buffer), (mp_obj_t)&embed_set_io_buffer_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_run), (mp_obj_t)&embed_run_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_disable_irq), (mp_obj_t)&embed_disable_irq_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_enable_irq), (mp_obj_t)&embed_enable_irq_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_FLAGS_IF), (mp_obj_t)&embed_FLAGS_IF_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_WAPY), (mp_obj_t)&embed_WAPY_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_print), (mp_obj_t)&embed_os_print_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_write), (mp_obj_t)&embed_os_write_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_stderr), (mp_obj_t)&embed_os_stderr_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&embed_log_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_builtins_vars), (mp_obj_t)&embed_builtins_vars_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_state_loop), (mp_obj_t)&embed_os_state_loop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_showloop), (mp_obj_t)&embed_os_showloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hideloop), (mp_obj_t)&embed_os_hideloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hook), (mp_obj_t)&embed_os_hook_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ns), (mp_obj_t)&embed_time_ns_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ms), (mp_obj_t)&embed_time_ms_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_add), (mp_obj_t)&embed_ticks_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_diff), (mp_obj_t)&embed_ticks_diff_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_sleep_ms), (mp_obj_t)&embed_sleep_ms_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_sleep), (mp_obj_t)&embed_sleep_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_period), (mp_obj_t)&embed_ticks_period_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_read_useless), (mp_obj_t)&embed_os_read_useless_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_echosum1), (mp_obj_t)&embed_echosum1_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_callsome), (mp_obj_t)&embed_callsome_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_callpy), (mp_obj_t)&embed_callpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_hash_djb2), (mp_obj_t)&embed_hash_djb2_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_show_trace), (mp_obj_t)&embed_show_trace_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_address_of), (mp_obj_t)&embed_address_of_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy), (mp_obj_t)&embed_set_ffpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy_add), (mp_obj_t)&embed_set_ffpy_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_corepy), (mp_obj_t)&embed_corepy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_somecall), (mp_obj_t)&embed_somecall_obj }, + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(embed_dict, embed_dict_table); + + + +//const mp_obj_module_t STATIC +PyModuleDef module_embed = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&embed_dict, +}; + +// Register the module to make it available +MP_REGISTER_MODULE(MP_QSTR_embed, module_embed, MODULE_EMBED_ENABLED); + diff --git a/ports/wapy/cmod/common/example/example.c b/ports/wapy/cmod/common/example/example.c new file mode 100644 index 000000000..562e75069 --- /dev/null +++ b/ports/wapy/cmod/common/example/example.c @@ -0,0 +1,40 @@ + + +// 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); + + diff --git a/ports/wapy/cmod/common/example/micropython.mk b/ports/wapy/cmod/common/example/micropython.mk new file mode 100644 index 000000000..6ab6a4d54 --- /dev/null +++ b/ports/wapy/cmod/common/example/micropython.mk @@ -0,0 +1,9 @@ +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) + diff --git a/ports/wapy/cmod/wasi/embed.pym b/ports/wapy/cmod/wasi/embed.pym new file mode 100644 index 000000000..f65fce9f0 --- /dev/null +++ b/ports/wapy/cmod/wasi/embed.pym @@ -0,0 +1,340 @@ +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// +// you *must* take care of return type yourself, glue code can only return None for you. + +#if __LVGL__ +#error LVGL +static int show_os_loop(int state) { + return 0; +} +#define state_os_loop show_os_loop +#else + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#endif + +extern struct timespec t_timespec; +extern struct timeval t_timeval; + +#if defined(__WASI__) + extern FILE *fd_logger; +#endif + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include + +//#include +//#include + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +#define WAPY_VALUE (1) +extern int VMFLAGS_IF; +extern int show_os_loop(int state); +extern int state_os_loop(int state); +//extern unsigned long long TV_NS ; + + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + + +# code trace +# ========================================== +qstr trace_prev_file = 1/*MP_QSTR_*/; +qstr trace_file = 1/*MP_QSTR_*/; + +size_t trace_prev_line; +size_t trace_line; + +int trace_on; + +# corepy +# ========================================== + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +# finalizers +# ========================================== + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +//========================================================================== + + + +def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max ; + cstr[0]=0; + +def run(runstr : const_char_p = "[]"): + # os interfacing, this is not really running except you could have eval() on the other side + # this is actually for passing json / cbor etc ... + + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(fd_logger, "buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + } + + + + +def CLI(): + VMFLAGS_IF--; + +def STI(): + VMFLAGS_IF++; + +def FLAGS_IF() -> int: + return mp_obj_new_int_from_uint(VMFLAGS_IF); + +def WAPY() -> int: +#if defined(__EMSCRIPTEN__) || defined(__WASI__) + return mp_obj_new_int_from_uint(1); +#else + return mp_obj_new_int_from_uint(0); +#endif + + +def os_print( data : const_char_p = "{}" ) -> void : + #fprintf(fd_logger, "embed.os_write(%lu)\n", strlen(data) ); + fprintf( stdout , "%s\n" , data ); + +def os_write( data : const_char_p = "{}" ) -> void : + #fprintf(fd_logger, "embed.os_write(%lu)\n", strlen(data) ); + fprintf( stdout , "%s" , data ); + +def os_stderr( data : const_char_p = "" ) -> void : + fprintf(stderr, "embed.os_stderr(%s)\n", data ); + +def log( data : const_char_p = "" ) -> void : + fprintf(fd_logger, "py: %s\n", data ); + + +def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + return mod_globals; + + +def os_compile(source_file : const_char_p="", mpy_file : const_char_p="") -> void: + vstr_t vstr; + + if (argc == 2 && argv[1] != mp_const_none) { + //done by glue code + } else { + vstr_init(&vstr, strlen(source_file) + 5); // +5 for NUL and .mpy + vstr_add_str(&vstr, source_file); + if (vstr.len > 3 && memcmp(&vstr.buf[vstr.len - 3], ".py", 3) == 0) { + // remove .py extension to replace with .mpy + vstr_cut_tail_bytes(&vstr, 3); + } + vstr_add_str(&vstr, ".mpy"); + mpy_file = vstr_null_terminated_str(&vstr); + } + #if MICROPY_PERSISTENT_CODE_SAVE + mp_lexer_t *lex = mp_lexer_new_from_file(source_file); + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + + mp_raw_code_t *rc = mp_compile_to_raw_code(&parse_tree, + qstr_from_str(source_file), + false); + + // mp_raw_code_save_file(rc, mpy_file); + #else + fprintf(fd_logger, "os_compile: MICROPY_PERSISTENT_CODE_SAVE not enabled\n"); + #endif + +def os_state_loop(state:int = 0)->int: + return mp_obj_new_int( state_os_loop(state) ); + +def os_showloop()->void : + fprintf(fd_logger, "will show begin/end for os loop\n"); + show_os_loop(1); + +def os_hideloop()->void : + fprintf(fd_logger, "will hide begin/end for os loop\n"); + show_os_loop(0); + + +def os_hook() ->void: + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass ; + printf("coro task started\n"); + coropass(); + } + } + + +// https://www.python.org/dev/peps/pep-0564/ +// https://vstinner.github.io/python37-pep-564-nanoseconds.html +def time_ns() -> int: + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000000000 + t_timespec.tv_nsec ; + return mp_obj_new_int_from_ull(ul) + +// upy +def time_ms() -> int: + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000 ; + return mp_obj_new_int_from_ull(ul); + +def ticks_add(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks + delta ); + +def ticks_diff(ticks : int=0, delta : int=0)->int: + return mp_obj_new_int_from_ull( ticks - delta ); + + +def ticks_period() -> int: + return mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); + + +def callpy(fn: const_char_p ="") -> void: + fprintf(fd_logger, "embed.callpy[%s]\n", fn ) + pycore(fn) + +def hash_djb2(cstr : const_char_p = "") -> _int_from_unsigned_long: + unsigned long hash = 5381 + int c + while ((c = *cstr++)): + hash = ((hash << 5) + hash) + c + return hash % 0xFFFFFFFF + +def show_trace() -> int: + trace_on = 1 + fprintf(fd_logger, "TRACE[%s:%zu -> %s:%zu]\n", qstr_str(trace_prev_file), trace_prev_line, qstr_str(trace_file), trace_line) + return trace_prev_line-1 + + +def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + return ptrvalue; + +def set_ffpy(fn: void_p) -> void: + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn ; + +def set_ffpy_add(fn: void_p) -> void: + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn ; + + +def corepy(fn: const_char_p = "") -> _int_from_ptr: + fprintf(fd_logger, "embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy): + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + + diff --git a/ports/wapy/cmod/wasi/embed/micropython.mk b/ports/wapy/cmod/wasi/embed/micropython.mk new file mode 100644 index 000000000..b214da743 --- /dev/null +++ b/ports/wapy/cmod/wasi/embed/micropython.mk @@ -0,0 +1,8 @@ + +EMBED_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(wildcard $(EMBED_MOD_DIR)/*.c) + +# add module folder to include paths if needed +CFLAGS_USERMOD += -I$(EMBED_MOD_DIR) diff --git a/ports/wapy/cmod/wasi/embed/modembed.c b/ports/wapy/cmod/wasi/embed/modembed.c new file mode 100644 index 000000000..ce58d6973 --- /dev/null +++ b/ports/wapy/cmod/wasi/embed/modembed.c @@ -0,0 +1,843 @@ +/* http://github.com/pmp-p target pym:/data/cross/wapy/cmod/wasi/embed.pym */ +/* + embed.c AUTO-GENERATED by /data/git/wapy-lib/modgen/__main__.py +*/ + +// ======= STATIC HEADER ======== + +#include +#include +#include // for free() + +#include "py/obj.h" +#include "py/runtime.h" + +#ifndef STATIC +#define STATIC static +#endif + + +#define None mp_const_none +#define bytes(cstr) PyBytes_FromString(cstr) +#define PyMethodDef const mp_map_elem_t +#define PyModuleDef const mp_obj_module_t + +#define mp_obj_get_double mp_obj_get_float +#define mp_obj_new_int_from_ptr mp_obj_new_int_from_ull + +#define mp_obj_new_int_from_unsigned_long mp_obj_new_int_from_uint +#define unsigned_long unsigned long + + + + + +// embed exports +void print(mp_obj_t str) { + mp_obj_print(str, PRINT_STR); + mp_obj_print(mp_obj_new_str_via_qstr("\n",1), PRINT_STR); +} + +void +null_pointer_exception(void) { + fprintf(stderr, "59: null pointer exception in function pointer call\n"); +} + +mp_obj_t +PyBytes_FromString(char *string){ + vstr_t vstr; + vstr_init_len(&vstr, strlen(string)); + strcpy(vstr.buf, string); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} + +const char *nullbytes = ""; +//static int orem_id = 0; + + +// =========== embedded header from .pym ============ + +// modgen +// python annotations describe the C types you need, argv is always a variable size array of mp_obj_t. +// glue code will do its best to do the conversion or init. +// +// you *must* take care of return type yourself, glue code can only return None for you. + +#if __LVGL__ +#error LVGL +static int show_os_loop(int state) { + return 0; +} +#define state_os_loop show_os_loop +#else + extern int show_os_loop(int state); + extern int state_os_loop(int state); +#endif + +extern struct timespec t_timespec; +extern struct timeval t_timeval; + +#if defined(__WASI__) + extern FILE *fd_logger; +#endif + +#include "py/emitglue.h" + +#include "py/lexer.h" +#include "py/compile.h" + +#include + +//#include +//#include + +#include "py/smallint.h" + +char * cstr ; +size_t cstr_max=0; + +#define WAPY_VALUE (1) +extern int VMFLAGS_IF; +extern int show_os_loop(int state); +extern int state_os_loop(int state); +//extern unsigned long long TV_NS ; + + +extern mp_lexer_t* mp_lexer_new_from_file(const char *filename); + +#if MICROPY_PERSISTENT_CODE_SAVE + extern void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename); +// Save .mpy file to file system + int raw_code_save_file(mp_raw_code_t *rc, const char *filename) { return 0; } +#endif + +#if NO_NLR +mp_obj_t execute_from_str(const char *str) { + return (mp_obj_t)0; +} +#else +mp_obj_t execute_from_str(const char *str) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + qstr src_name = 1/*MP_QSTR_*/; + mp_lexer_t *lex = mp_lexer_new_from_str_len(src_name, str, strlen(str), false); + mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&pt, src_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + return 0; + } else { + // uncaught exception + return (mp_obj_t)nlr.ret_val; + } +} +#endif + + +STATIC void coropass(void) { + const char sched[] = + "__module__ = __import__('sys').modules.get('asyncio',None);" + "__module__ = __module__ and __module__.get_event_loop().step()"; + + const char *sched_ptr = &sched[0]; + execute_from_str( sched_ptr); + MP_STATE_PORT(coro_call_counter++); +} + + +// code trace + +// ========================================== + +qstr trace_prev_file = 1/*MP_QSTR_*/; +qstr trace_file = 1/*MP_QSTR_*/; + +size_t trace_prev_line; +size_t trace_line; + +int trace_on; + +// corepy + +// ========================================== + + +static mp_obj_t * ffpy = NULL; +static mp_obj_t * ffpy_add = NULL; + +void +pyv(mp_obj_t value) { + if (ffpy_add) + mp_call_function_n_kw((mp_obj_t *)ffpy_add, 1, 0, &value); +} + +mp_obj_t +pycore(const char *fn) { + uintptr_t __creturn__ = 0; + qstr qfn ; + qfn = qstr_from_strn(fn, strlen(fn)); + mp_obj_t qst = MP_OBJ_NEW_QSTR(qfn); + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "FFYPY[%p->%s]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &qst); + } + return mp_obj_new_int_from_ptr(__creturn__); +} + +// finalizers + +// ========================================== + + +typedef struct _on_del_t { + mp_obj_base_t base; + mp_obj_t fun; +} on_del_t; + +extern mp_obj_type_t mp_type_on_del; + +STATIC mp_obj_t new_on_del(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + on_del_t *o = m_new_obj_with_finaliser(on_del_t); + o->base.type = &mp_type_on_del; + o->fun = args[0]; + return o; +} + +STATIC mp_obj_t on_del_del(mp_obj_t self_in) { + return mp_call_function_0(((on_del_t *)self_in)->fun); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(on_del_del_obj, on_del_del); + +STATIC void on_del_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL && attr == MP_QSTR___del__) { + dest[0] = MP_OBJ_FROM_PTR(&on_del_del_obj); + dest[1] = self_in; + } +} + +mp_obj_type_t mp_type_on_del = { + {&mp_type_type}, + .name = MP_QSTR_on_del, + .make_new = new_on_del, + .attr = on_del_attr, +}; + + +//========================================================================== + + + +// end of embedded header +// ============================================================= + + + +STATIC mp_obj_t // void -> void +embed_set_io_buffer(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_io_buffer(ptr: int = 0, ptr_max: int = 0): + // ('int', '0') => int = 0 + + int ptr; // ('int', 'ptr', '0') + if (argc>0) { ptr = mp_obj_get_int(argv[0]); } else { ptr = 0 ; } + + + int ptr_max; // ('int', 'ptr_max', '0') + if (argc>1) { ptr_max = mp_obj_get_int(argv[1]); } else { ptr_max = 0 ; } + + + // ------- method body (try/finally) ----- + uintptr_t * addr; + addr = (uintptr_t *)(uintptr_t)ptr; + cstr = (char *)addr; + cstr_max = (size_t)ptr_max; + cstr[0]=0; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_io_buffer_obj, 0, 2, embed_set_io_buffer); + + + +STATIC mp_obj_t // void -> void +embed_run(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def run(runstr : const_char_p = "[]"): + // ('const_char_p', '"[]"') => const char * = "[]" + + const char *runstr; // ('const char *', 'runstr', '"[]"') + if (argc>0) { runstr = mp_obj_str_get_str(argv[0]); } else { runstr = mp_obj_new_str_via_qstr("[]",2); } + + + // ------- method body (try/finally) ----- + size_t ln = strlen(runstr); + if ( ln < cstr_max ) { + strcpy(cstr,runstr); + } else { + fprintf(fd_logger, "buffer overrun in embed.run %zu >= %zu", ln, cstr_max); + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_run_obj, 0, 1, embed_run); + + + +STATIC mp_obj_t // void -> void +embed_CLI(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF--; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_CLI_obj, 0, 0, embed_CLI); + + + +STATIC mp_obj_t // void -> void +embed_STI(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + VMFLAGS_IF++; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_STI_obj, 0, 0, embed_STI); + + + +STATIC mp_obj_t // int -> int +embed_FLAGS_IF(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint(VMFLAGS_IF); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_FLAGS_IF_obj, 0, 0, embed_FLAGS_IF); + + + +STATIC mp_obj_t // int -> int +embed_WAPY(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint(1); goto lreturn__; }; + { __creturn__ = (long)mp_obj_new_int_from_uint(0); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_WAPY_obj, 0, 0, embed_WAPY); + + + +STATIC mp_obj_t // void -> void +embed_os_print(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_print( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s\n" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_print_obj, 0, 1, embed_os_print); + + + +STATIC mp_obj_t // void -> void +embed_os_write(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_write( data : const_char_p = "{}" ) -> void : + // ('const_char_p', '"{}"') => const char * = "{}" + + const char *data; // ('const char *', 'data', '"{}"') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("{}",2); } + + + // ------- method body (try/finally) ----- + fprintf( stdout , "%s" , data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_write_obj, 0, 1, embed_os_write); + + + +STATIC mp_obj_t // void -> void +embed_os_stderr(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_stderr( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(stderr, "embed.os_stderr(%s)\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_stderr_obj, 0, 1, embed_os_stderr); + + + +STATIC mp_obj_t // void -> void +embed_log(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def log( data : const_char_p = "" ) -> void : + // ('const_char_p', '""') => const char * = "" + + const char *data; // ('const char *', 'data', '""') + if (argc>0) { data = mp_obj_str_get_str(argv[0]); } else { data = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "py: %s\n", data ); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_log_obj, 0, 1, embed_log); + + + +STATIC mp_obj_t // dict -> dict +embed_builtins_vars(size_t argc, const mp_obj_t *argv) { +// opt: no finally dict slot + mp_obj_dict_t * __creturn__; + // def builtins_vars(module_obj : mp_obj_t = None ) -> dict: + // ('mp_obj_t', 'None') => mp_obj_t = None + + mp_obj_t module_obj; // ('mp_obj_t', 'module_obj', 'NULL') + if (argc>0) { module_obj = (mp_obj_t)argv[0]; } + else { module_obj = NULL ; } + + + // ------- method body (try/finally) ----- + mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); + { __creturn__ = (mp_obj_dict_t *)mod_globals; goto lreturn__; }; +lreturn__: return (mp_obj_t)__creturn__ ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_builtins_vars_obj, 0, 1, embed_builtins_vars); + + + +STATIC mp_obj_t // void -> void +embed_os_compile(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def os_compile(source_file : const_char_p="", mpy_file : const_char_p="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *source_file; // ('const char *', 'source_file', '""') + if (argc>0) { source_file = mp_obj_str_get_str(argv[0]); } else { source_file = mp_obj_new_str_via_qstr("",0); } + + + const char *mpy_file; // ('const char *', 'mpy_file', '""') + if (argc>1) { mpy_file = mp_obj_str_get_str(argv[1]); } else { mpy_file = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + vstr_t vstr; + if (argc == 2 && argv[1] != mp_const_none) { + } else { + vstr_init(&vstr, strlen(source_file) + 5); // +5 for NUL and .mpy; + vstr_add_str(&vstr, source_file); + if (vstr.len > 3 && memcmp(&vstr.buf[vstr.len - 3], ".py", 3) == 0) { + vstr_cut_tail_bytes(&vstr, 3); + }; + vstr_add_str(&vstr, ".mpy"); + mpy_file = vstr_null_terminated_str(&vstr); + }; + mp_lexer_t *lex = mp_lexer_new_from_file(source_file); + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_raw_code_t *rc = mp_compile_to_raw_code(&parse_tree, + qstr_from_str(source_file), + false); + fprintf(fd_logger, "os_compile: MICROPY_PERSISTENT_CODE_SAVE not enabled\n"); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_compile_obj, 0, 2, embed_os_compile); + + + +STATIC mp_obj_t // int -> int +embed_os_state_loop(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def os_state_loop(state:int = 0)->int: + // ('int', '0') => int = 0 + + int state; // ('int', 'state', '0') + if (argc>0) { state = mp_obj_get_int(argv[0]); } else { state = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int( state_os_loop(state) ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_state_loop_obj, 0, 1, embed_os_state_loop); + + + +STATIC mp_obj_t // void -> void +embed_os_showloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "will show begin/end for os loop\n"); + show_os_loop(1); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_showloop_obj, 0, 0, embed_os_showloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hideloop(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "will hide begin/end for os loop\n"); + show_os_loop(0); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hideloop_obj, 0, 0, embed_os_hideloop); + + + +STATIC mp_obj_t // void -> void +embed_os_hook(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + + // ------- method body (try/finally) ----- + void (*void_ptr)(int) = MP_STATE_PORT(PyOS_InputHook); + if ( void_ptr != NULL ) { + printf("PyOS_InputHook %p TODO: allow py callback ptr\n", void_ptr); + } else { + printf("PyOS_InputHook undef TODO: allow py callback ptr\n"); + if ( !MP_STATE_PORT(coro_call_counter)) { + MP_STATE_PORT(PyOS_InputHook) = &coropass; + printf("coro task started\n"); + coropass(); + }; + }; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_os_hook_obj, 0, 0, embed_os_hook); + + + +STATIC mp_obj_t // int -> int +embed_time_ns(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000000000 + t_timespec.tv_nsec; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ns_obj, 0, 0, embed_time_ns); + + + +STATIC mp_obj_t // int -> int +embed_time_ms(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + wa_clock_gettime(CLOCK_MONOTONIC, &t_timespec); + unsigned long long ul = t_timespec.tv_sec * 1000 + t_timespec.tv_nsec / 1000000; + { __creturn__ = (long)mp_obj_new_int_from_ull(ul); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_time_ms_obj, 0, 0, embed_time_ms); + + + +STATIC mp_obj_t // int -> int +embed_ticks_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_add(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks + delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_add_obj, 0, 2, embed_ticks_add); + + + +STATIC mp_obj_t // int -> int +embed_ticks_diff(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + // def ticks_diff(ticks : int=0, delta : int=0)->int: + // ('int', '0') => int = 0 + + int ticks; // ('int', 'ticks', '0') + if (argc>0) { ticks = mp_obj_get_int(argv[0]); } else { ticks = 0 ; } + + + int delta; // ('int', 'delta', '0') + if (argc>1) { delta = mp_obj_get_int(argv[1]); } else { delta = 0 ; } + + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_ull( ticks - delta ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_diff_obj, 0, 2, embed_ticks_diff); + + + +STATIC mp_obj_t // int -> int +embed_ticks_period(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + { __creturn__ = (long)mp_obj_new_int_from_uint( MICROPY_PY_UTIME_TICKS_PERIOD ); goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_ticks_period_obj, 0, 0, embed_ticks_period); + + + +STATIC mp_obj_t // void -> void +embed_callpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def callpy(fn: const_char_p ="") -> void: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.callpy[%s]\n", fn ); + pycore(fn); +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_callpy_obj, 0, 1, embed_callpy); + + + +STATIC mp_obj_t // _int_from_unsigned_long -> _int_from_unsigned_long +embed_hash_djb2(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_unsigned_long slot + unsigned_long __creturn__ = 0; + // def hash_djb2(cstr : const_char_p = "") -> _int_from_unsigned_long: + // ('const_char_p', '""') => const char * = "" + + const char *cstr; // ('const char *', 'cstr', '""') + if (argc>0) { cstr = mp_obj_str_get_str(argv[0]); } else { cstr = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + unsigned long hash = 5381; + int c; + while ((c = *cstr++)) { + hash = ((hash << 5) + hash) + c; + } + { __creturn__ = (unsigned_long)hash % 0xFFFFFFFF; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_unsigned_long(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_hash_djb2_obj, 0, 1, embed_hash_djb2); + + + +STATIC mp_obj_t // int -> int +embed_show_trace(size_t argc, const mp_obj_t *argv) { +// opt: no finally int slot + long __creturn__ = 0; + + // ------- method body (try/finally) ----- + trace_on = 1; + fprintf(fd_logger, "TRACE[%s:%zu -> %s:%zu]\n", qstr_str(trace_prev_file), trace_prev_line, qstr_str(trace_file), trace_line); + { __creturn__ = (long)trace_prev_line-1; goto lreturn__; }; +lreturn__: return mp_obj_new_int(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_show_trace_obj, 0, 0, embed_show_trace); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_address_of(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def address_of(ptr : void_p = NULL ) -> _int_from_ptr: + // ('void_p', 'NULL') => void * = NULL + + void * ptr; // ('void *', 'ptr', 'NULL') + if (argc>0) { ptr = (void *)argv[0]; } + else { ptr = NULL ; } + + + // ------- method body (try/finally) ----- + uintptr_t * ptraddr = (uintptr_t *)ptr; + uintptr_t ptrvalue = (uintptr_t)(void *)ptraddr; + { __creturn__ = (uintptr_t)ptrvalue; goto lreturn__; }; +lreturn__: return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_address_of_obj, 0, 1, embed_address_of); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_obj, 0, 1, embed_set_ffpy); + + + +STATIC mp_obj_t // void -> void +embed_set_ffpy_add(size_t argc, const mp_obj_t *argv) { +// opt: no finally void slot +// opt : void return + // def set_ffpy_add(fn: void_p) -> void: + // ('void_p', None) => void * = NULL + + void * fn; // ('void *', 'fn', 'NULL') + if (argc>0) { fn = (void *)argv[0]; } + else { fn = NULL ; } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.ffpy[%p]\n", fn ); + ffpy_add = (mp_obj_t *)fn; +return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_set_ffpy_add_obj, 0, 1, embed_set_ffpy_add); + + + +STATIC mp_obj_t // _int_from_ptr -> _int_from_ptr +embed_corepy(size_t argc, const mp_obj_t *argv) { +// opt: no finally _int_from_ptr slot + uintptr_t __creturn__ = 0; + // def corepy(fn: const_char_p = "") -> _int_from_ptr: + // ('const_char_p', '""') => const char * = "" + + const char *fn; // ('const char *', 'fn', '""') + if (argc>0) { fn = mp_obj_str_get_str(argv[0]); } else { fn = mp_obj_new_str_via_qstr("",0); } + + + // ------- method body (try/finally) ----- + fprintf(fd_logger, "embed.ffipy[%p(%s)]\n", ffpy, fn ); + if (ffpy) { + mp_call_function_n_kw((mp_obj_t *)ffpy, 1, 0, &argv[0]); + } +return mp_obj_new_int_from_ptr(__creturn__); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(embed_corepy_obj, 0, 1, embed_corepy); + + + +// global module dict : + + + +STATIC const mp_map_elem_t embed_dict_table[] = { +// builtins + {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_embed) }, + {MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_OBJ_NEW_QSTR(MP_QSTR_flashrom) }, + +// extensions + {MP_OBJ_NEW_QSTR(MP_QSTR_on_del), MP_ROM_PTR(&mp_type_on_del) }, + + +// Classes : + + +// __main__ + {MP_OBJ_NEW_QSTR(MP_QSTR_set_io_buffer), (mp_obj_t)&embed_set_io_buffer_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_run), (mp_obj_t)&embed_run_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_CLI), (mp_obj_t)&embed_CLI_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_STI), (mp_obj_t)&embed_STI_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_FLAGS_IF), (mp_obj_t)&embed_FLAGS_IF_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_WAPY), (mp_obj_t)&embed_WAPY_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_print), (mp_obj_t)&embed_os_print_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_write), (mp_obj_t)&embed_os_write_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_stderr), (mp_obj_t)&embed_os_stderr_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&embed_log_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_builtins_vars), (mp_obj_t)&embed_builtins_vars_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_compile), (mp_obj_t)&embed_os_compile_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_state_loop), (mp_obj_t)&embed_os_state_loop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_showloop), (mp_obj_t)&embed_os_showloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hideloop), (mp_obj_t)&embed_os_hideloop_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_os_hook), (mp_obj_t)&embed_os_hook_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ns), (mp_obj_t)&embed_time_ns_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_time_ms), (mp_obj_t)&embed_time_ms_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_add), (mp_obj_t)&embed_ticks_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_diff), (mp_obj_t)&embed_ticks_diff_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_ticks_period), (mp_obj_t)&embed_ticks_period_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_callpy), (mp_obj_t)&embed_callpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_hash_djb2), (mp_obj_t)&embed_hash_djb2_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_show_trace), (mp_obj_t)&embed_show_trace_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_address_of), (mp_obj_t)&embed_address_of_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy), (mp_obj_t)&embed_set_ffpy_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_set_ffpy_add), (mp_obj_t)&embed_set_ffpy_add_obj }, + {MP_OBJ_NEW_QSTR(MP_QSTR_corepy), (mp_obj_t)&embed_corepy_obj }, + +// {NULL, NULL, 0, NULL} // cpython +}; + +STATIC MP_DEFINE_CONST_DICT(embed_dict, embed_dict_table); + + + +//const mp_obj_module_t STATIC +PyModuleDef module_embed = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&embed_dict, +}; + +// Register the module to make it available +MP_REGISTER_MODULE(MP_QSTR_embed, module_embed, MODULE_EMBED_ENABLED); + diff --git a/ports/wapy/cmod/wasi/example b/ports/wapy/cmod/wasi/example new file mode 120000 index 000000000..de77548b3 --- /dev/null +++ b/ports/wapy/cmod/wasi/example @@ -0,0 +1 @@ +../common/example \ No newline at end of file diff --git a/ports/wapy/core/fdfile.h b/ports/wapy/core/fdfile.h new file mode 100644 index 000000000..69a9b6be4 --- /dev/null +++ b/ports/wapy/core/fdfile.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * 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_FDFILE_H +#define MICROPY_INCLUDED_UNIX_FDFILE_H + +#include "py/obj.h" + +typedef struct _mp_obj_fdfile_t { + mp_obj_base_t base; + int fd; +} mp_obj_fdfile_t; + +extern const mp_obj_type_t mp_type_fileio; +extern const mp_obj_type_t mp_type_textio; + +#endif // MICROPY_INCLUDED_UNIX_FDFILE_H diff --git a/ports/wapy/core/file.wapy.c b/ports/wapy/core/file.wapy.c new file mode 100644 index 000000000..bc0a66ae4 --- /dev/null +++ b/ports/wapy/core/file.wapy.c @@ -0,0 +1,323 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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 +#include +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/builtin.h" +#include "py/mphal.h" +#include "py/mpthread.h" +#include "fdfile.h" + +#if MICROPY_PY_IO && !MICROPY_VFS + +#ifdef _WIN32 +#define fsync _commit +#endif + +#ifdef MICROPY_CPYTHON_COMPAT +STATIC int check_fd_is_open(const mp_obj_fdfile_t *o) { + if (o->fd < 0) { + mp_raise_ValueError_o("I/O operation on closed file"); + return 1; + } + return 0; +} +#else +#define check_fd_is_open(o) +#endif + +extern const mp_obj_type_t mp_type_fileio; +extern const mp_obj_type_t mp_type_textio; + +STATIC void fdfile_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", mp_obj_get_type_str(self_in), self->fd); +} + +STATIC mp_uint_t fdfile_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { + mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + mp_int_t r = read(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + if (r == -1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + return r; +} + +STATIC mp_uint_t fdfile_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { + mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + #if MICROPY_PY_OS_DUPTERM + if (o->fd <= STDERR_FILENO) { + mp_hal_stdout_tx_strn(buf, size); + return size; + } + #endif + MP_THREAD_GIL_EXIT(); + mp_int_t r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + while (r == -1 && errno == EINTR) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + return MP_STREAM_ERROR; + } + MP_THREAD_GIL_EXIT(); + r = write(o->fd, buf, size); + MP_THREAD_GIL_ENTER(); + } + if (r == -1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + return r; +} + +STATIC mp_uint_t fdfile_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + mp_obj_fdfile_t *o = MP_OBJ_TO_PTR(o_in); + if (check_fd_is_open(o)) { + return MP_STREAM_ERROR; + } + switch (request) { + case MP_STREAM_SEEK: { + struct mp_stream_seek_t *s = (struct mp_stream_seek_t*)arg; + MP_THREAD_GIL_EXIT(); + off_t off = lseek(o->fd, s->offset, s->whence); + MP_THREAD_GIL_ENTER(); + if (off == (off_t)-1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + s->offset = off; + return 0; + } + case MP_STREAM_FLUSH: + MP_THREAD_GIL_EXIT(); + int ret = fsync(o->fd); + MP_THREAD_GIL_ENTER(); + if (ret == -1) { + *errcode = errno; + return MP_STREAM_ERROR; + } + return 0; + case MP_STREAM_CLOSE: + MP_THREAD_GIL_EXIT(); + close(o->fd); + MP_THREAD_GIL_ENTER(); + #ifdef MICROPY_CPYTHON_COMPAT + o->fd = -1; + #endif + return 0; + case MP_STREAM_GET_FILENO: + return o->fd; + default: + *errcode = EINVAL; + return MP_STREAM_ERROR; + } +} + +STATIC mp_obj_t fdfile___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + return mp_stream_close(args[0]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fdfile___exit___obj, 4, 4, fdfile___exit__); + +STATIC mp_obj_t fdfile_fileno(mp_obj_t self_in) { + mp_obj_fdfile_t *self = MP_OBJ_TO_PTR(self_in); + if (check_fd_is_open(self)) { + return MP_OBJ_NULL; + } + return MP_OBJ_NEW_SMALL_INT(self->fd); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(fdfile_fileno_obj, fdfile_fileno); + +// Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO, +// but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor +STATIC const mp_arg_t file_open_args[] = { + { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r)} }, + { MP_QSTR_buffering, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, +}; +#define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args) + +#if defined(__EMSCRIPTEN__) +extern int wasm_file_open(const char *url); +#endif + +STATIC mp_obj_t fdfile_open(const mp_obj_type_t *type, mp_arg_val_t *args) { + mp_obj_fdfile_t *o = m_new_obj(mp_obj_fdfile_t); + const char *mode_s = mp_obj_str_get_str(args[1].u_obj); +#if defined(__EMSCRIPTEN__) +int can_online = 0; +#endif + + int mode_rw = 0, mode_x = 0; + while (*mode_s) { + switch (*mode_s++) { + case 'r': + + mode_rw = O_RDONLY; + break; + case 'w': + mode_rw = O_WRONLY; + mode_x = O_CREAT | O_TRUNC; + break; + case 'a': + mode_rw = O_WRONLY; + mode_x = O_CREAT | O_APPEND; + break; + case '+': + mode_rw = O_RDWR; + break; + #if MICROPY_PY_IO_FILEIO + // If we don't have io.FileIO, then files are in text mode implicitly + case 'b': + type = &mp_type_fileio; + break; + case 't': + type = &mp_type_textio; + break; + #endif + } + } + + o->base.type = type; + + mp_obj_t fid = args[0].u_obj; + + if (mp_obj_is_small_int(fid)) { + o->fd = MP_OBJ_SMALL_INT_VALUE(fid); + return MP_OBJ_FROM_PTR(o); + } + + const char *fname = mp_obj_str_get_str(fid); + int fd = 0; + MP_THREAD_GIL_EXIT(); +#if defined(__EMSCRIPTEN__) + if (can_online) + fd = wasm_file_open( fname ); +#endif + if (!fd) + fd=open(fname, mode_x | mode_rw, 0644); + MP_THREAD_GIL_ENTER(); + if (fd == -1) { + return mp_raise_OSError_o(errno); + } + o->fd = fd; + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t fdfile_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; + mp_arg_parse_all_kw_array(n_args, n_kw, args, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals); + return fdfile_open(type, arg_vals); +} + +STATIC const mp_rom_map_elem_t rawfile_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&fdfile_fileno_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) }, + { MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&fdfile___exit___obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table); + +#if MICROPY_PY_IO_FILEIO +STATIC const mp_stream_p_t fileio_stream_p = { + .read = fdfile_read, + .write = fdfile_write, + .ioctl = fdfile_ioctl, +}; + +const mp_obj_type_t mp_type_fileio = { + { &mp_type_type }, + .name = MP_QSTR_FileIO, + .print = fdfile_print, + .make_new = fdfile_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &fileio_stream_p, + .locals_dict = (mp_obj_dict_t*)&rawfile_locals_dict, +}; +#endif + +STATIC const mp_stream_p_t textio_stream_p = { + .read = fdfile_read, + .write = fdfile_write, + .ioctl = fdfile_ioctl, + .is_text = true, +}; + +const mp_obj_type_t mp_type_textio = { + { &mp_type_type }, + .name = MP_QSTR_TextIOWrapper, + .print = fdfile_print, + .make_new = fdfile_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &textio_stream_p, + .locals_dict = (mp_obj_dict_t*)&rawfile_locals_dict, +}; + +// Factory function for I/O stream classes +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + // TODO: analyze buffering args and instantiate appropriate type + mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; + mp_arg_parse_all(n_args, args, kwargs, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals); + return fdfile_open(&mp_type_textio, arg_vals); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); + +const mp_obj_fdfile_t mp_sys_stdin_obj = { .base = {&mp_type_textio}, .fd = STDIN_FILENO }; +const mp_obj_fdfile_t mp_sys_stdout_obj = { .base = {&mp_type_textio}, .fd = STDOUT_FILENO }; +const mp_obj_fdfile_t mp_sys_stderr_obj = { .base = {&mp_type_textio}, .fd = STDERR_FILENO }; + +#endif // MICROPY_PY_IO && !MICROPY_VFS diff --git a/ports/wapy/core/ringbuf_b.c b/ports/wapy/core/ringbuf_b.c new file mode 100644 index 000000000..152cd634b --- /dev/null +++ b/ports/wapy/core/ringbuf_b.c @@ -0,0 +1,110 @@ + + +/****************************************************************************** + + Copyright (c) 2018 EmbedJournal + + 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. + + Author : Siddharth Chandrasekaran + Email : siddharth@embedjournal.com + Date : Sun Aug 5 09:42:31 IST 2018 + +******************************************************************************/ + +#include "ringbuf_b.h" + +int rbb_append(rbb_t *c, uint8_t data) +{ + int next; + + next = c->head + 1; // next is where head will point to after this write. + if (next >= c->maxlen) + next = 0; + + // if the head + 1 == tail, circular buffer is full. Notice that one slot + // is always left empty to differentiate empty vs full condition + if (next == c->tail) + return -1; + + c->buffer[c->head] = data; // Load data and then move + c->head = next; // head to next data offset. + return 0; // return success to indicate successful push. +} + +int rbb_is_empty(rbb_t *c) { + return (c->head == c->tail); // if the head == tail, we don't have any data +} + + +int rbb_pop(rbb_t *c, uint8_t *data) +{ + int next; + + if (c->head == c->tail) // if the head == tail, we don't have any data + return 0; + + next = c->tail + 1; // next is where tail will point to after this read. + if(next >= c->maxlen) + next = 0; + + *data = c->buffer[c->tail]; // Read data and then move + c->tail = next; // tail to next offset. + return 1; // return success to indicate successful pop. +} + +int rbb_free_space(rbb_t *c) +{ + int freeSpace; + freeSpace = c->tail - c->head; + if (freeSpace <= 0) + freeSpace += c->maxlen; + return freeSpace - 1; // -1 to account for the always-empty slot. +} + +#ifdef C_UTILS_TESTING +/* To test this module, + * $ gcc -Wall -DC_UTILS_TESTING rinbguf_b.c + * $ ./a.out +*/ + +RBB_T(my_circ_buf, 32); + +#include + +int main() +{ + uint8_t out_data=0, in_data = 0x55; + + if (rbb_append(&my_circ_buf, in_data)) { + printf("Out of space in CB\n"); + return -1; + } + + if (rbb_pop(&my_circ_buf, &out_data)) { + printf("CB is empty\n"); + return -1; + } + + printf("Push: 0x%x\n", in_data); + printf("Pop: 0x%x\n", out_data); + return 0; +} + +#endif diff --git a/ports/wapy/core/ringbuf_b.h b/ports/wapy/core/ringbuf_b.h new file mode 100644 index 000000000..bb14ea73f --- /dev/null +++ b/ports/wapy/core/ringbuf_b.h @@ -0,0 +1,75 @@ +/****************************************************************************** + + Copyright (c) 2018 EmbedJournal + + 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. + + Author : Siddharth Chandrasekaran + Email : siddharth@embedjournal.com + Date : Sun Aug 5 09:42:31 IST 2018 + +******************************************************************************/ +#ifndef RINGBUF_B_H +#define RINGBUF_B_H + +#include + +typedef struct { + uint8_t * const buffer; + int head; + int tail; + const int maxlen; +} rbb_t; + +#define RBB_T(x,y) \ + uint8_t x##_data_space[y+1]; \ + rbb_t x = { \ + .buffer = x##_data_space, \ + .head = 0, \ + .tail = 0, \ + .maxlen = y+1 \ + } + +/* + * Method: circ_buf_pop + * Returns: + * 0 - Success + * -1 - Empty + */ +int rbb_pop(rbb_t *c, uint8_t *data); + +/* + * Method: circ_buf_push + * Returns: + * 0 - Success + * -1 - Out of space + */ +int rbb_append(rbb_t *c, uint8_t data); + +/* + * Method: rbb_free_space + * Returns: number of bytes available + */ +int rbb_free_space(rbb_t *c); + + +int rbb_is_empty(rbb_t *c); + +#endif /* RINGBUF_B_H */ + diff --git a/ports/wapy/core/ringbuf_o.c b/ports/wapy/core/ringbuf_o.c new file mode 100644 index 000000000..d87802828 --- /dev/null +++ b/ports/wapy/core/ringbuf_o.c @@ -0,0 +1,221 @@ +/* + ringbuf_o.c - Library for implementing a simple Ring Buffer on Arduino boards. + Created by D. Aaron Wisner (daw268@cornell.edu) + January 17, 2015. + Released into the public domain. +*/ +#include +#include +#include +#include +#include + +#include "ringbuf_o.h" + + + +typedef struct rbo_t { + // Invariant: end and start is always in bounds + unsigned char *buf; + unsigned int len, size, start, end, elements; + + // Private: + int (*next_end_index) (rbo_t*); + int (*incr_end_index) (rbo_t*); + + int (*incr_start_index) (rbo_t*); + + //public: + // Returns true if full + uint8_t (*isFull) (rbo_t*); + // Returns true if empty + uint8_t (*isEmpty) (rbo_t*); + // Returns number of elemnts in buffer + unsigned int (*numElements)(rbo_t*); + // Add Event, Returns index where added in buffer, -1 on full buffer + int (*add) (rbo_t*, const void*); + // Returns pointer to nth element, NULL when nth element is empty + void *(*peek) (rbo_t*, unsigned int); + // Removes element and copies it to location pointed to by void * + // Returns pointer passed in, NULL on empty buffer + void *(*pull) (rbo_t*, void *); + +} rbo_t; + +/////// Constructor ////////// +rbo_t* +rbo_t_new(int size, int len) { + rbo_t *self = (rbo_t *)malloc(sizeof(rbo_t)); + if (!self) return NULL; + memset(self, 0, sizeof(rbo_t)); + if (rbo_init(self, size, len) < 0) + { + free(self); + return NULL; + } + return self; +} + +int +rbo_init(rbo_t *self, int size, int len) { + self->buf = (unsigned char *)malloc(size*len); + if (!self->buf) return -1; + memset(self->buf, 0, size*len); + + self->size = size; + self->len = len; + self->start = 0; + self->end = 0; + self->elements = 0; + + self->next_end_index = &rbo_next_end_index; + self->incr_end_index = &rbo_incr_end; + self->incr_start_index = &rbo_incr_start; + self->isFull = &rbo_is_full; + self->isEmpty = &rbo_is_empty; + self->add = &rbo_append; + self->numElements = &rbo_count; + self->peek = &rbo_peek; + self->pull = &rbo_pop; + return 0; +} +/////// Deconstructor ////////// +int +rbo_delete(rbo_t *self) { + free(self->buf); + free(self); + return 0; +} + +/////// PRIVATE METHODS ////////// + +// get next empty index +int +rbo_next_end_index(rbo_t *self) { + //buffer is full + if (self->isFull(self)) return -1; + //if empty dont incriment + return (self->end+(unsigned int)!self->isEmpty(self))%self->len; +} + +// incriment index of rbo_t struct, only call if safe to do so +int +rbo_incr_end(rbo_t *self) { + self->end = (self->end+1)%self->len; + return self->end; +} + + +// incriment index of rbo_t struct, only call if safe to do so +int +rbo_incr_start(rbo_t *self) { + self->start = (self->start+1)%self->len; + return self->start; +} + +/////// PUBLIC METHODS ////////// + +// Add an object struct to rbo_t +int +rbo_append(rbo_t *self, const void *object) { + int index; + // Perform all atomic operations + RB_ATOMIC_START + { + index = self->next_end_index(self); + //if not full + if (index >= 0) + { + memcpy(self->buf + index*self->size, object, self->size); + if (!self->isEmpty(self)) self->incr_end_index(self); + self->elements++; + } + } + RB_ATOMIC_END + + return index; +} + +// Return pointer to num element, return null on empty or num out of bounds +void* +rbo_peek(rbo_t *self, unsigned int num) { + void *ret = NULL; + // Perform all atomic opertaions + RB_ATOMIC_START + { + //empty or out of bounds + if (self->isEmpty(self) || num > self->elements - 1) ret = NULL; + else ret = &self->buf[((self->start + num)%self->len)*self->size]; + } + RB_ATOMIC_END + + return ret; +} + +// Returns and removes first buffer element +void* +rbo_pop(rbo_t *self, void *object) { + void *ret = NULL; + // Perform all atomic opertaions + RB_ATOMIC_START + { + if (self->isEmpty(self)) ret = NULL; + // Else copy Object + else + { + memcpy(object, self->buf+self->start*self->size, self->size); + self->elements--; + // don't increment start if removing last element + if (!self->isEmpty(self)) self->incr_start_index(self); + ret = object; + } + } + RB_ATOMIC_END + + return ret; +} + +// Returns number of elemnts in buffer +unsigned int +rbo_count(rbo_t *self) { + unsigned int elements; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + elements = self->elements; + } + RB_ATOMIC_END + + return elements; +} + +// Returns true if buffer is full +uint8_t +rbo_is_full(rbo_t *self) { + uint8_t ret; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + ret = self->elements == self->len; + } + RB_ATOMIC_END + + return ret; +} + +// Returns true if buffer is empty +uint8_t +rbo_is_empty(rbo_t *self) { + uint8_t ret; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + ret = !self->elements; + } + RB_ATOMIC_END + + return ret; +} diff --git a/ports/wapy/core/ringbuf_o.h b/ports/wapy/core/ringbuf_o.h new file mode 100644 index 000000000..07fb7ae58 --- /dev/null +++ b/ports/wapy/core/ringbuf_o.h @@ -0,0 +1,117 @@ + +/* + rbo_t.h - Library for implementing a simple Ring Buffer on Arduino boards. + Created by D. Aaron Wisner (daw268@cornell.edu) + January 17, 2015. + Released into the public domain. +*/ + + +#ifndef RINGBUF_O_H +#define RINGBUF_O_H + +#if __ARDUINO__ + #if defined(__WASI__) + #else + #include "Arduino.h" + #define RB_ATOMIC_START ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + #define RB_ATOMIC_END } + #endif +#else + #define RB_ATOMIC_START + #define RB_ATOMIC_END +#endif + +#if defined(ARDUINO_ARCH_AVR) + #include + +#elif defined(ARDUINO_ARCH_ESP8266) + #ifndef __STRINGIFY + #define __STRINGIFY(a) #a + #endif + + #ifndef xt_rsil + #define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state)); state;})) + #endif + + #ifndef xt_wsr_ps + #define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory") + #endif + + #define RB_ATOMIC_START do { uint32_t _savedIS = xt_rsil(15) ; + #define RB_ATOMIC_END xt_wsr_ps(_savedIS) ;} while(0); +#else + + #if defined(__EMSCRIPTEN__) || defined(__WASI__) || defined(__ANDROID__) + + #else + #if __ARDUINO__ + #error ("This library only supports AVR and ESP8266 Boards.") + #endif + #endif + +#endif + + +typedef struct rbo_t rbo_t; + + +#ifdef __cplusplus +extern "C" { +#endif + +rbo_t *rbo_t_new(int size, int len); + +int rbo_init(rbo_t *self, int size, int len); +int rbo_delete(rbo_t *self); + +int rbo_next_end_index(rbo_t *self); +int rbo_incr_end(rbo_t *self); +int rbo_incr_start(rbo_t *self); +int rbo_append(rbo_t *self, const void *object); +void *rbo_peek(rbo_t *self, unsigned int num); +void *rbo_pop(rbo_t *self, void *object); +uint8_t rbo_is_full(rbo_t *self); +uint8_t rbo_is_empty(rbo_t *self); +unsigned int rbo_count(rbo_t *self); + +#ifdef __cplusplus +} +#endif + + +// For those of you who cant live without pretty C++ objects.... +#ifdef __cplusplus +class rbo_tC +{ + +public: + rbo_tC(int size, int len) { buf = rbo_t_new(size, len); } + ~rbo_tC() { rbo_delete(buf); } + + uint8_t isFull() { return rbo_is_full(buf); } + uint8_t isEmpty() { return rbo_is_empty(buf); } + unsigned int numElements() { return rbo_count(buf); } + + unsigned int add(const void *object) { return rbo_append(buf, object); } + void *peek(unsigned int num) { return rbo_peek(buf, num); } + void *pull(void *object) { return rbo_pop(buf, object); } + + // Use this to check if memory allocation failed + uint8_t allocFailed() { return !buf; } + +private: + rbo_t *buf; +}; +#endif + + +#endif // RINGBUF_O_H + + + + + + + + diff --git a/ports/wapy/core/vfs.c b/ports/wapy/core/vfs.c new file mode 100644 index 000000000..11b543178 --- /dev/null +++ b/ports/wapy/core/vfs.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include + +#include "py/nlr.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/repl.h" +#include "py/gc.h" +#include "lib/utils/pyexec.h" +#include "py/mphal.h" + +#include "../upython.h" + + +#if !MICROPY_VFS +// FIXME: +extern mp_import_stat_t +wasm_find_module(const char *modname); + +mp_import_stat_t mp_import_stat(const char *path) { + cdbg("25:stat(%s) : ", path); + return wasm_find_module(path); +} +#endif + + +#if !MICROPY_READER_POSIX && MICROPY_EMIT_WASM + + +mp_lexer_t * +mp_lexer_new_from_file(const char *filename) { + FILE *file = fopen(filename,"r"); + if (!file) { + //printf("404: fopen(%s)\n", filename); + clog("404: fopen(%s)\n", filename); + return NULL; + } + fseek(file, 0, SEEK_END); + long long size_of_file = ftell(file); + //cdbg("mp_lexer_new_from_file(%s size=%lld)\n", filename, (long long)size_of_file ); + fseek(file, 0, SEEK_SET); + + char * cbuf = malloc(size_of_file+1); + fread(cbuf, size_of_file, 1, file); + cbuf[size_of_file]=0; + + if (cbuf == NULL) { + cdbg("READ ERROR: mp_lexer_new_from_file(%s size=%lld)\n", filename, (long long)size_of_file ); + return NULL; + } + mp_lexer_t* lex = mp_lexer_new_from_str_len(qstr_from_str(filename), cbuf, strlen(cbuf), 0); + free(cbuf); // <- remove that and emcc -shared will break + return lex; +} +#if MICROPY_HELPER_LEXER_UNIX + +mp_lexer_t *mp_lexer_new_from_fd(qstr filename, int fd, bool close_fd) { + mp_reader_t reader; + mp_reader_new_file_from_fd(&reader, fd, close_fd); + return mp_lexer_new(filename, reader); +} + +#endif + +#else +extern mp_lexer_t *mp_lexer_new_from_file(const char *filename); +#endif + + + +#if !MICROPY_READER_POSIX && MICROPY_EMIT_WASM + +#include +#include +#include +#include "py/mperrno.h" + +typedef struct _mp_reader_posix_t { + bool close_fd; + int fd; + size_t len; + size_t pos; + byte buf[20]; +} mp_reader_posix_t; + +STATIC mp_uint_t +mp_reader_posix_readbyte(void *data) { + mp_reader_posix_t *reader = (mp_reader_posix_t*)data; + if (reader->pos >= reader->len) { + if (reader->len == 0) { + return MP_READER_EOF; + } else { + int n = read(reader->fd, reader->buf, sizeof(reader->buf)); + if (n <= 0) { + reader->len = 0; + return MP_READER_EOF; + } + reader->len = n; + reader->pos = 0; + } + } + return reader->buf[reader->pos++]; +} + +STATIC void +mp_reader_posix_close(void *data) { + mp_reader_posix_t *reader = (mp_reader_posix_t*)data; + if (reader->close_fd) { + close(reader->fd); + } + m_del_obj(mp_reader_posix_t, reader); +} + +void +mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { + mp_reader_posix_t *rp = m_new_obj(mp_reader_posix_t); + rp->close_fd = close_fd; + rp->fd = fd; + int n = read(rp->fd, rp->buf, sizeof(rp->buf)); + if (n == -1) { + if (close_fd) { + close(fd); + } + mp_raise_OSError_o(errno); + } + rp->len = n; + rp->pos = 0; + reader->data = rp; + reader->readbyte = mp_reader_posix_readbyte; + reader->close = mp_reader_posix_close; +} + +void +mp_reader_new_file(mp_reader_t *reader, const char *filename) { + int fd = open(filename, O_RDONLY, 0644); + if (fd < 0) { + mp_raise_OSError_o(errno); + } + mp_reader_new_file_from_fd(reader, fd, true); +} +#endif diff --git a/ports/wapy/cpy.c b/ports/wapy/cpy.c new file mode 100644 index 000000000..4215e0b84 --- /dev/null +++ b/ports/wapy/cpy.c @@ -0,0 +1,148 @@ +#include + +#include "py/objlist.h" +#include "py/runtime.h" + +#define HEAP_SIZE 64 * 1024 * 1024 + +// TODO: use a circular buffer for everything io related +#define MP_IO_SHM_SIZE 65535 + +#define MP_IO_SIZE 512 + +#define IO_KBD ( MP_IO_SHM_SIZE - (1 * MP_IO_SIZE) ) + +#define False 0 +#define True !False + +#if MICROPY_ENABLE_PYSTACK +//#define MP_STACK_SIZE 16384 +#define MP_STACK_SIZE 32768 +static mp_obj_t pystack[MP_STACK_SIZE]; +#endif + +static char *stack_top; + +/* +extern void gc_init(void *start, void *end); +extern void mp_pystack_init(void *start, void *end); +extern void mp_init(void); +*/ + + +void +Py_Initialize(){ + int stack_dummy; + stack_top = (char *) &stack_dummy; + char *heap = (char *) malloc(HEAP_SIZE * sizeof(char)); + gc_init(heap, heap + HEAP_SIZE); + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); + mp_init(); + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); + mp_obj_list_init(mp_sys_argv, 0); +} + + + +struct +wPyInterpreterState { + char *shm_stdio ; + char *shm_input_event_0; +}; + + +struct +wPyThreadState { + struct wPyInterpreterState *interp; +}; + + +//static +struct wPyInterpreterState i_main; +//static +struct wPyThreadState i_state; + +// should check null +char * +shm_ptr() { + return &i_main.shm_stdio[0]; +} + + +char * +wPy_NewInterpreter() { + i_main.shm_stdio = (char *) malloc(MP_IO_SHM_SIZE); + if (!i_main.shm_stdio) + fprintf(stdout, "74:shm_stdio malloc failed\n"); + i_main.shm_stdio[0] = 0; + for (int i = 0; i < MP_IO_SHM_SIZE; i++) + i_main.shm_stdio[i] = 0; + i_state.interp = &i_main; + #if MICROPY_REPL_EVENT_DRIVEN + pyexec_event_repl_init(); + #else + fprintf(stdout,"no repl\n"); + #endif + return shm_ptr(); +} + + + + + + + +#include "py/compile.h" + +int +pyeval(const char *src, mp_parse_input_kind_t input_kind) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), False); + + if (lex == NULL) { + fprintf(stderr, "148:NULL LEXER->handle_uncaught_exception\n%s\n", src); + return 0; + } + { +#if MICROPY_ENABLE_COMPILER + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, False); + if (module_fun != MP_OBJ_NULL) { + mp_obj_t ret = mp_call_function_0(module_fun); + if (ret != MP_OBJ_NULL) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + //mp_raise(obj); + + } else { + return 1; //success + } + } + + } else { + // uncaught exception + fprintf(stderr, "150:NULL FUNCTION\n"); + } +#else + #pragma message "compiler is disabled, no repl" +#endif + } + return 0; +} + +int +PyRun_SimpleString(const char *command) { + int retval = 0; + if (command) { + retval = pyeval(command, MP_PARSE_FILE_INPUT); + } else { + if (i_main.shm_stdio[0]) { + retval = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); + i_main.shm_stdio[0] = 0; + } + } + return retval; +} + diff --git a/ports/wapy/flash/frozen_str.py b/ports/wapy/flash/frozen_str.py new file mode 100644 index 000000000..24ea25efc --- /dev/null +++ b/ports/wapy/flash/frozen_str.py @@ -0,0 +1,2 @@ +print('Hello FROZEN STR') + diff --git a/ports/wapy/modbuiltins_no_nlr.c b/ports/wapy/modbuiltins_no_nlr.c new file mode 100644 index 000000000..550d849c3 --- /dev/null +++ b/ports/wapy/modbuiltins_no_nlr.c @@ -0,0 +1,819 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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/smallint.h" +#include "py/objint.h" +#include "py/objstr.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stream.h" + +#if MICROPY_PY_BUILTINS_FLOAT +#include +#endif + +#if MICROPY_PY_IO +extern struct _mp_dummy_t mp_sys_stdout_obj; // type is irrelevant, just need pointer +#endif + +// args[0] is function from class body +// args[1] is class name +// args[2:] are base objects +STATIC mp_obj_t mp_builtin___build_class__(size_t n_args, const mp_obj_t *args) { + assert(2 <= n_args); + + // set the new classes __locals__ object + mp_obj_dict_t *old_locals = mp_locals_get(); + mp_obj_t class_locals = mp_obj_new_dict(0); + mp_locals_set(MP_OBJ_TO_PTR(class_locals)); + + // call the class code + mp_obj_t cell = mp_call_function_0(args[0]); + + // restore old __locals__ object + mp_locals_set(old_locals); + + // get the class type (meta object) from the base objects + mp_obj_t meta; + if (n_args == 2) { + // no explicit bases, so use 'type' + meta = MP_OBJ_FROM_PTR(&mp_type_type); + } else { + // use type of first base object + meta = MP_OBJ_FROM_PTR(mp_obj_get_type(args[2])); + } + + // TODO do proper metaclass resolution for multiple base objects + + // create the new class using a call to the meta object + mp_obj_t meta_args[3]; + meta_args[0] = args[1]; // class name + meta_args[1] = mp_obj_new_tuple(n_args - 2, args + 2); // tuple of bases + meta_args[2] = class_locals; // dict of members + mp_obj_t new_class = mp_call_function_n_kw(meta, 3, 0, meta_args); + + // store into cell if neede + if (cell != mp_const_none) { + mp_obj_cell_set(cell, new_class); + } + + return new_class; +} +MP_DEFINE_CONST_FUN_OBJ_VAR(mp_builtin___build_class___obj, 2, mp_builtin___build_class__); + +STATIC mp_obj_t mp_builtin_abs(mp_obj_t o_in) { + return mp_unary_op(MP_UNARY_OP_ABS, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_abs_obj, mp_builtin_abs); + +STATIC mp_obj_t mp_builtin_all(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + if (!mp_obj_is_true(item)) { + return mp_const_false; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return mp_const_true; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_all_obj, mp_builtin_all); + +STATIC mp_obj_t mp_builtin_any(mp_obj_t o_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(o_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + if (mp_obj_is_true(item)) { + return mp_const_true; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return mp_const_false; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_any_obj, mp_builtin_any); + +STATIC mp_obj_t mp_builtin_bin(mp_obj_t o_in) { + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_b_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_bin_obj, mp_builtin_bin); + +STATIC mp_obj_t mp_builtin_callable(mp_obj_t o_in) { + if (mp_obj_is_callable(o_in)) { + return mp_const_true; + } else { + return mp_const_false; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_callable_obj, mp_builtin_callable); + +STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) { +#pragma message "interning everything is *not* clever (pmpp-p)" + #if MICROPY_PY_BUILTINS_STR_UNICODE + mp_uint_t c = mp_obj_get_int(o_in); + uint8_t str[4]; + int len = 0; + if (c < 0x80) { + *str = c; + len = 1; + } else if (c < 0x800) { + str[0] = (c >> 6) | 0xC0; + str[1] = (c & 0x3F) | 0x80; + len = 2; + } else if (c < 0x10000) { + str[0] = (c >> 12) | 0xE0; + str[1] = ((c >> 6) & 0x3F) | 0x80; + str[2] = (c & 0x3F) | 0x80; + len = 3; + } else if (c < 0x110000) { + str[0] = (c >> 18) | 0xF0; + str[1] = ((c >> 12) & 0x3F) | 0x80; + str[2] = ((c >> 6) & 0x3F) | 0x80; + str[3] = (c & 0x3F) | 0x80; + len = 4; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(0x110000)")); + } + return mp_obj_new_str_via_qstr((char *)str, len); + #else + mp_int_t ord = mp_obj_get_int(o_in); + if (0 <= ord && ord <= 0xff) { + uint8_t str[1] = {ord}; + + return mp_obj_new_str_via_qstr((char *)str, 1); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(256)")); + } + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr); + +STATIC mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { + mp_obj_t dir = mp_obj_new_list(0, NULL); + if (n_args == 0) { + // Make a list of names in the local namespace + mp_obj_dict_t *dict = mp_locals_get(); + for (size_t i = 0; i < dict->map.alloc; i++) { + if (mp_map_slot_is_filled(&dict->map, i)) { + mp_obj_list_append(dir, dict->map.table[i].key); + } + } + } else { // n_args == 1 + // Make a list of names in the given object + // Implemented by probing all possible qstrs with mp_load_method_maybe + size_t nqstr = QSTR_TOTAL(); + for (size_t i = MP_QSTR_ + 1; i < nqstr; ++i) { + mp_obj_t dest[2]; + mp_load_method_protected(args[0], i, dest, false); + if (dest[0] != MP_OBJ_NULL) { + #if MICROPY_PY_ALL_SPECIAL_METHODS + // Support for __dir__: see if we can dispatch to this special method + // This relies on MP_QSTR__dir__ being first after MP_QSTR_ + if (i == MP_QSTR___dir__ && dest[1] != MP_OBJ_NULL) { + return mp_call_method_n_kw(0, 0, dest); + } + #endif + mp_obj_list_append(dir, MP_OBJ_NEW_QSTR(i)); + } + } + } + return dir; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); + +STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { + return mp_binary_op(MP_BINARY_OP_DIVMOD, o1_in, o2_in); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_divmod_obj, mp_builtin_divmod); + +STATIC mp_obj_t mp_builtin_hash(mp_obj_t o_in) { + // result is guaranteed to be a (small) int + return mp_unary_op(MP_UNARY_OP_HASH, o_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hash_obj, mp_builtin_hash); + +STATIC mp_obj_t mp_builtin_hex(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_x), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_x_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_hex_obj, mp_builtin_hex); + +#if MICROPY_PY_BUILTINS_INPUT + +#include "py/mphal.h" +#include "lib/mp-readline/readline.h" + +// A port can define mp_hal_readline if they want to use a custom function here +#ifndef mp_hal_readline +#define mp_hal_readline readline +#endif + +STATIC mp_obj_t mp_builtin_input(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_print(args[0], PRINT_STR); + } + vstr_t line; + vstr_init(&line, 16); + int ret = mp_hal_readline(&line, ""); + if (ret == CHAR_CTRL_C) { + mp_raise_type(&mp_type_KeyboardInterrupt); + } + if (line.len == 0 && ret == CHAR_CTRL_D) { + mp_raise_type(&mp_type_EOFError); + } + return mp_obj_new_str_from_vstr(&mp_type_str, &line); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_input_obj, 0, 1, mp_builtin_input); + +#endif + +STATIC mp_obj_t mp_builtin_iter(mp_obj_t o_in) { + return mp_getiter(o_in, NULL); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_iter_obj, mp_builtin_iter); + +#if MICROPY_PY_BUILTINS_MIN_MAX + +STATIC mp_obj_t mp_builtin_min_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs, mp_uint_t op) { + mp_map_elem_t *key_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_key), MP_MAP_LOOKUP); + mp_map_elem_t *default_elem; + mp_obj_t key_fn = key_elem == NULL ? MP_OBJ_NULL : key_elem->value; + if (n_args == 1) { + // given an iterable + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? item : mp_call_function_1(key_fn, item); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = item; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + if (best_obj == MP_OBJ_NULL) { + default_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_default), MP_MAP_LOOKUP); + if (default_elem != NULL) { + best_obj = default_elem->value; + } else { + return mp_raise_ValueError_o("arg is an empty sequence"); + } + } + return best_obj; + } else { + // given many args + mp_obj_t best_key = MP_OBJ_NULL; + mp_obj_t best_obj = MP_OBJ_NULL; + for (size_t i = 0; i < n_args; i++) { + mp_obj_t key = key_fn == MP_OBJ_NULL ? args[i] : mp_call_function_1(key_fn, args[i]); + if (best_obj == MP_OBJ_NULL || (mp_binary_op(op, key, best_key) == mp_const_true)) { + best_key = key; + best_obj = args[i]; + } + } + return best_obj; + } +} + +STATIC mp_obj_t mp_builtin_max(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_MORE); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_max_obj, 1, mp_builtin_max); + +STATIC mp_obj_t mp_builtin_min(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_builtin_min_max(n_args, args, kwargs, MP_BINARY_OP_LESS); +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_min_obj, 1, mp_builtin_min); + +#endif + +#if MICROPY_PY_BUILTINS_NEXT2 +STATIC mp_obj_t mp_builtin_next(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + mp_obj_t ret = mp_iternext_allow_raise(args[0]); + if (ret == MP_OBJ_STOP_ITERATION) { + mp_raise_type(&mp_type_StopIteration); + } else { + return ret; + } + } else { + mp_obj_t ret = mp_iternext(args[0]); + return ret == MP_OBJ_STOP_ITERATION ? args[1] : ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_next_obj, 1, 2, mp_builtin_next); +#else +STATIC mp_obj_t mp_builtin_next(mp_obj_t o) { + mp_obj_t ret = mp_iternext_allow_raise(o); + if (ret == MP_OBJ_STOP_ITERATION) { + mp_raise_type(&mp_type_StopIteration); + } else { + return ret; + } +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_next_obj, mp_builtin_next); +#endif + +STATIC mp_obj_t mp_builtin_oct(mp_obj_t o_in) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + return mp_binary_op(MP_BINARY_OP_MODULO, MP_OBJ_NEW_QSTR(MP_QSTR__percent__hash_o), o_in); + #else + mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR__brace_open__colon__hash_o_brace_close_), o_in }; + return mp_obj_str_format(MP_ARRAY_SIZE(args), args, NULL); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_oct_obj, mp_builtin_oct); + +STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) { + size_t len; + const byte *str = (const byte *)mp_obj_str_get_data(o_in, &len); + if (str == NULL) { + // exception + return MP_OBJ_NULL; + } + #if MICROPY_PY_BUILTINS_STR_UNICODE + if (mp_obj_is_str(o_in)) { + len = utf8_charlen(str, len); + if (len == 1) { + return mp_obj_new_int(utf8_get_char(str)); + } + } else + #endif + { + // a bytes object, or a str without unicode support (don't sign extend the char) + if (len == 1) { + return MP_OBJ_NEW_SMALL_INT(str[0]); + } + } + + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("ord expects a character")); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("ord() expected a character, but string of length %d found"), (int)len)); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_ord_obj, mp_builtin_ord); + +STATIC mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { + switch (n_args) { + case 2: + return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); + default: + #if !MICROPY_PY_BUILTINS_POW3 + mp_raise_NotImplementedError(MP_ERROR_TEXT("3-arg pow() not supported")); + #elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ + return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); + #else + return mp_obj_int_pow3(args[0], args[1], args[2]); + #endif + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_pow_obj, 2, 3, mp_builtin_pow); + +STATIC mp_obj_t mp_builtin_print(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_sep, ARG_end, ARG_file }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__space_)} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR__0x0a_)} }, + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + { MP_QSTR_file, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_sys_stdout_obj)} }, + #endif + }; + + // parse args (a union is used to reduce the amount of C stack that is needed) + union { + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + size_t len[2]; + } u; + mp_arg_parse_all(0, NULL, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, u.args); + + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + if (mp_get_stream_raise(u.args[ARG_file].u_obj, MP_STREAM_OP_WRITE) == NULL) { + return MP_OBJ_NULL; + } + mp_print_t print = {MP_OBJ_TO_PTR(u.args[ARG_file].u_obj), mp_stream_write_adaptor}; + #endif + + // extract the objects first because we are going to use the other part of the union + mp_obj_t sep = u.args[ARG_sep].u_obj; + mp_obj_t end = u.args[ARG_end].u_obj; + const char *sep_data = mp_obj_str_get_data(sep, &u.len[0]); + const char *end_data = mp_obj_str_get_data(end, &u.len[1]); + + for (size_t i = 0; i < n_args; i++) { + if (i > 0) { + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, sep_data, u.len[0]); + #else + mp_print_strn(&mp_plat_print, sep_data, u.len[0], 0, 0, 0); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_obj_print_helper(&print, pos_args[i], PRINT_STR); + #else + mp_obj_print_helper(&mp_plat_print, pos_args[i], PRINT_STR); + #endif + } + #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES + mp_stream_write_adaptor(print.data, end_data, u.len[1]); + #else + mp_print_strn(&mp_plat_print, end_data, u.len[1], 0, 0, 0); + #endif + if (MP_STATE_THREAD(active_exception) != NULL) { + // some printing function above had an exception + return MP_OBJ_NULL; + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_print_obj, 0, mp_builtin_print); + +STATIC mp_obj_t mp_builtin___repl_print__(mp_obj_t o) { + if (o != mp_const_none) { + mp_obj_print_helper(MP_PYTHON_PRINTER, o, PRINT_REPR); + mp_print_str(MP_PYTHON_PRINTER, "\n"); + #if MICROPY_CAN_OVERRIDE_BUILTINS + // Set "_" special variable + mp_obj_t dest[2] = {MP_OBJ_SENTINEL, o}; + mp_type_module.attr(MP_OBJ_FROM_PTR(&mp_module_builtins), MP_QSTR__, dest); + #endif + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin___repl_print___obj, mp_builtin___repl_print__); + +STATIC mp_obj_t mp_builtin_repr(mp_obj_t o_in) { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + mp_obj_print_helper(&print, o_in, PRINT_REPR); + return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_repr_obj, mp_builtin_repr); + +STATIC mp_obj_t mp_builtin_round(size_t n_args, const mp_obj_t *args) { + mp_obj_t o_in = args[0]; + if (mp_obj_is_int(o_in)) { + if (n_args <= 1) { + return o_in; + } + + #if !MICROPY_PY_BUILTINS_ROUND_INT + mp_raise_NotImplementedError(NULL); + #else + mp_int_t num_dig = mp_obj_get_int(args[1]); + if (num_dig >= 0) { + return o_in; + } + + mp_obj_t mult = mp_binary_op(MP_BINARY_OP_POWER, MP_OBJ_NEW_SMALL_INT(10), MP_OBJ_NEW_SMALL_INT(-num_dig)); + mp_obj_t half_mult = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, mult, MP_OBJ_NEW_SMALL_INT(2)); + mp_obj_t modulo = mp_binary_op(MP_BINARY_OP_MODULO, o_in, mult); + mp_obj_t rounded = mp_binary_op(MP_BINARY_OP_SUBTRACT, o_in, modulo); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, half_mult, modulo))) { + return rounded; + } else if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_MORE, modulo, half_mult))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + // round to even number + mp_obj_t floor = mp_binary_op(MP_BINARY_OP_FLOOR_DIVIDE, o_in, mult); + if (mp_obj_is_true(mp_binary_op(MP_BINARY_OP_AND, floor, MP_OBJ_NEW_SMALL_INT(1)))) { + return mp_binary_op(MP_BINARY_OP_ADD, rounded, mult); + } else { + return rounded; + } + } + #endif + } + #if MICROPY_PY_BUILTINS_FLOAT + mp_float_t val = mp_obj_get_float(o_in); + if (n_args > 1) { + mp_int_t num_dig = mp_obj_get_int(args[1]); + mp_float_t mult = MICROPY_FLOAT_C_FUN(pow)(10, (mp_float_t)num_dig); + // TODO may lead to overflow + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val * mult) / mult; + return mp_obj_new_float(rounded); + } + mp_float_t rounded = MICROPY_FLOAT_C_FUN(nearbyint)(val); + return mp_obj_new_int_from_float(rounded); + #else + mp_int_t r = mp_obj_get_int(o_in); + return mp_obj_new_int(r); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_round_obj, 1, 2, mp_builtin_round); + +STATIC mp_obj_t mp_builtin_sum(size_t n_args, const mp_obj_t *args) { + mp_obj_t value; + switch (n_args) { + case 1: + value = MP_OBJ_NEW_SMALL_INT(0); + break; + default: + value = args[1]; + break; + } + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + value = mp_binary_op(MP_BINARY_OP_ADD, value, item); + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return value; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_sum_obj, 1, 2, mp_builtin_sum); + +STATIC mp_obj_t mp_builtin_sorted(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args > 1) { + return mp_raise_TypeError_o("must use keyword argument for key function"); + } + mp_obj_t self = mp_type_list.make_new(&mp_type_list, 1, 0, args); + mp_obj_list_sort(1, &self, kwargs); + + return self; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_sorted_obj, 1, mp_builtin_sorted); + +// See mp_load_attr() if making any changes +static inline mp_obj_t mp_load_attr_default(mp_obj_t base, qstr attr, mp_obj_t defval) { + mp_obj_t dest[2]; + // use load_method, raising or not raising exception + ((defval == MP_OBJ_NULL) ? mp_load_method : mp_load_method_maybe)(base, attr, dest); + if (defval == MP_OBJ_NULL) { + mp_load_method(base, attr, dest); + } else { + mp_load_method_protected(base, attr, dest, false); + } + if (dest[0] == MP_OBJ_NULL) { + return defval; + } else if (dest[1] == MP_OBJ_NULL) { + // load_method returned just a normal attribute + return dest[0]; + } else { + // load_method returned a method, so build a bound method object + return mp_obj_new_bound_meth(dest[0], dest[1]); + } +} + +STATIC mp_obj_t mp_builtin_getattr(size_t n_args, const mp_obj_t *args) { + mp_obj_t defval = MP_OBJ_NULL; + if (n_args > 2) { + defval = args[2]; + } + return mp_load_attr_default(args[0], mp_obj_str_get_qstr(args[1]), defval); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_getattr_obj, 2, 3, mp_builtin_getattr); + +STATIC mp_obj_t mp_builtin_setattr(mp_obj_t base, mp_obj_t attr, mp_obj_t value) { + if (mp_store_attr(base, mp_obj_str_get_qstr(attr), value) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(mp_builtin_setattr_obj, mp_builtin_setattr); + +#if MICROPY_CPYTHON_COMPAT +STATIC mp_obj_t mp_builtin_delattr(mp_obj_t base, mp_obj_t attr) { + return mp_builtin_setattr(base, attr, MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_delattr_obj, mp_builtin_delattr); +#endif + +STATIC mp_obj_t mp_builtin_hasattr(mp_obj_t object_in, mp_obj_t attr_in) { + qstr attr = mp_obj_str_get_qstr(attr_in); + if (attr == MP_QSTR_NULL) { + // exception + return MP_OBJ_NULL; + } + mp_obj_t dest[2]; + if (mp_load_method_protected(object_in, attr, dest, false) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_obj_new_bool(dest[0] != MP_OBJ_NULL); +} +MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_hasattr_obj, mp_builtin_hasattr); + +STATIC mp_obj_t mp_builtin_globals(void) { + return MP_OBJ_FROM_PTR(mp_globals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_globals_obj, mp_builtin_globals); + +STATIC mp_obj_t mp_builtin_locals(void) { + return MP_OBJ_FROM_PTR(mp_locals_get()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_locals_obj, mp_builtin_locals); + +// These are defined in terms of MicroPython API functions right away +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_id_obj, mp_obj_id); +MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_len_obj, mp_obj_len); + +STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_builtins) }, + + // built-in core functions + { MP_ROM_QSTR(MP_QSTR___build_class__), MP_ROM_PTR(&mp_builtin___build_class___obj) }, + { MP_ROM_QSTR(MP_QSTR___import__), MP_ROM_PTR(&mp_builtin___import___obj) }, +// { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + { MP_ROM_QSTR(MP_QSTR_displayhook), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + + // built-in types + { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_PTR(&mp_type_bool) }, + { MP_ROM_QSTR(MP_QSTR_bytes), MP_ROM_PTR(&mp_type_bytes) }, + #if MICROPY_PY_BUILTINS_BYTEARRAY + { MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&mp_type_bytearray) }, + #endif + #if MICROPY_PY_BUILTINS_COMPLEX + { MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_PTR(&mp_type_complex) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dict), MP_ROM_PTR(&mp_type_dict) }, + #if MICROPY_PY_BUILTINS_ENUMERATE + { MP_ROM_QSTR(MP_QSTR_enumerate), MP_ROM_PTR(&mp_type_enumerate) }, + #endif + #if MICROPY_PY_BUILTINS_FILTER + { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&mp_type_filter) }, + #endif + #if MICROPY_PY_BUILTINS_FLOAT + { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_PTR(&mp_type_float) }, + #endif + #if MICROPY_PY_BUILTINS_SET && MICROPY_PY_BUILTINS_FROZENSET + { MP_ROM_QSTR(MP_QSTR_frozenset), MP_ROM_PTR(&mp_type_frozenset) }, + #endif + { MP_ROM_QSTR(MP_QSTR_int), MP_ROM_PTR(&mp_type_int) }, + { MP_ROM_QSTR(MP_QSTR_list), MP_ROM_PTR(&mp_type_list) }, + { MP_ROM_QSTR(MP_QSTR_map), MP_ROM_PTR(&mp_type_map) }, + #if MICROPY_PY_BUILTINS_MEMORYVIEW + { MP_ROM_QSTR(MP_QSTR_memoryview), MP_ROM_PTR(&mp_type_memoryview) }, + #endif + { MP_ROM_QSTR(MP_QSTR_object), MP_ROM_PTR(&mp_type_object) }, + #if MICROPY_PY_BUILTINS_PROPERTY + { MP_ROM_QSTR(MP_QSTR_property), MP_ROM_PTR(&mp_type_property) }, + #endif + { MP_ROM_QSTR(MP_QSTR_range), MP_ROM_PTR(&mp_type_range) }, + #if MICROPY_PY_BUILTINS_REVERSED + { MP_ROM_QSTR(MP_QSTR_reversed), MP_ROM_PTR(&mp_type_reversed) }, + #endif + #if MICROPY_PY_BUILTINS_SET + { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&mp_type_set) }, + #endif + #if MICROPY_PY_BUILTINS_SLICE + { MP_ROM_QSTR(MP_QSTR_slice), MP_ROM_PTR(&mp_type_slice) }, + #endif + { MP_ROM_QSTR(MP_QSTR_str), MP_ROM_PTR(&mp_type_str) }, + { MP_ROM_QSTR(MP_QSTR_super), MP_ROM_PTR(&mp_type_super) }, + { MP_ROM_QSTR(MP_QSTR_tuple), MP_ROM_PTR(&mp_type_tuple) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&mp_type_type) }, + { MP_ROM_QSTR(MP_QSTR_zip), MP_ROM_PTR(&mp_type_zip) }, + + { MP_ROM_QSTR(MP_QSTR_classmethod), MP_ROM_PTR(&mp_type_classmethod) }, + { MP_ROM_QSTR(MP_QSTR_staticmethod), MP_ROM_PTR(&mp_type_staticmethod) }, + + // built-in objects + { MP_ROM_QSTR(MP_QSTR_Ellipsis), MP_ROM_PTR(&mp_const_ellipsis_obj) }, + #if MICROPY_PY_BUILTINS_NOTIMPLEMENTED + { MP_ROM_QSTR(MP_QSTR_NotImplemented), MP_ROM_PTR(&mp_const_notimplemented_obj) }, + #endif + + // built-in user functions + { MP_ROM_QSTR(MP_QSTR_abs), MP_ROM_PTR(&mp_builtin_abs_obj) }, + { MP_ROM_QSTR(MP_QSTR_all), MP_ROM_PTR(&mp_builtin_all_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&mp_builtin_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_bin), MP_ROM_PTR(&mp_builtin_bin_obj) }, + { MP_ROM_QSTR(MP_QSTR_callable), MP_ROM_PTR(&mp_builtin_callable_obj) }, + #if MICROPY_PY_BUILTINS_COMPILE + { MP_ROM_QSTR(MP_QSTR_compile), MP_ROM_PTR(&mp_builtin_compile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_chr), MP_ROM_PTR(&mp_builtin_chr_obj) }, + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_delattr), MP_ROM_PTR(&mp_builtin_delattr_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dir), MP_ROM_PTR(&mp_builtin_dir_obj) }, + { MP_ROM_QSTR(MP_QSTR_divmod), MP_ROM_PTR(&mp_builtin_divmod_obj) }, + #if MICROPY_PY_BUILTINS_EVAL_EXEC + { MP_ROM_QSTR(MP_QSTR_eval), MP_ROM_PTR(&mp_builtin_eval_obj) }, + { MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&mp_builtin_exec_obj) }, + #endif + #if MICROPY_PY_BUILTINS_EXECFILE + { MP_ROM_QSTR(MP_QSTR_execfile), MP_ROM_PTR(&mp_builtin_execfile_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_getattr), MP_ROM_PTR(&mp_builtin_getattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_setattr), MP_ROM_PTR(&mp_builtin_setattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_globals), MP_ROM_PTR(&mp_builtin_globals_obj) }, + { MP_ROM_QSTR(MP_QSTR_hasattr), MP_ROM_PTR(&mp_builtin_hasattr_obj) }, + { MP_ROM_QSTR(MP_QSTR_hash), MP_ROM_PTR(&mp_builtin_hash_obj) }, + #if MICROPY_PY_BUILTINS_HELP + { MP_ROM_QSTR(MP_QSTR_help), MP_ROM_PTR(&mp_builtin_help_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_hex), MP_ROM_PTR(&mp_builtin_hex_obj) }, + { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&mp_builtin_id_obj) }, + #if MICROPY_PY_BUILTINS_INPUT + { MP_ROM_QSTR(MP_QSTR_input), MP_ROM_PTR(&mp_builtin_input_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_isinstance), MP_ROM_PTR(&mp_builtin_isinstance_obj) }, + { MP_ROM_QSTR(MP_QSTR_issubclass), MP_ROM_PTR(&mp_builtin_issubclass_obj) }, + { MP_ROM_QSTR(MP_QSTR_iter), MP_ROM_PTR(&mp_builtin_iter_obj) }, + { MP_ROM_QSTR(MP_QSTR_len), MP_ROM_PTR(&mp_builtin_len_obj) }, + { MP_ROM_QSTR(MP_QSTR_locals), MP_ROM_PTR(&mp_builtin_locals_obj) }, + #if MICROPY_PY_BUILTINS_MIN_MAX + { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&mp_builtin_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&mp_builtin_min_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_next), MP_ROM_PTR(&mp_builtin_next_obj) }, + { MP_ROM_QSTR(MP_QSTR_oct), MP_ROM_PTR(&mp_builtin_oct_obj) }, + { MP_ROM_QSTR(MP_QSTR_ord), MP_ROM_PTR(&mp_builtin_ord_obj) }, + { MP_ROM_QSTR(MP_QSTR_pow), MP_ROM_PTR(&mp_builtin_pow_obj) }, + { MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) }, + { MP_ROM_QSTR(MP_QSTR_repr), MP_ROM_PTR(&mp_builtin_repr_obj) }, + { MP_ROM_QSTR(MP_QSTR_round), MP_ROM_PTR(&mp_builtin_round_obj) }, + { MP_ROM_QSTR(MP_QSTR_sorted), MP_ROM_PTR(&mp_builtin_sorted_obj) }, + { MP_ROM_QSTR(MP_QSTR_sum), MP_ROM_PTR(&mp_builtin_sum_obj) }, + + // built-in exceptions + { MP_ROM_QSTR(MP_QSTR_BaseException), MP_ROM_PTR(&mp_type_BaseException) }, + { MP_ROM_QSTR(MP_QSTR_ArithmeticError), MP_ROM_PTR(&mp_type_ArithmeticError) }, + { MP_ROM_QSTR(MP_QSTR_AssertionError), MP_ROM_PTR(&mp_type_AssertionError) }, + { MP_ROM_QSTR(MP_QSTR_AttributeError), MP_ROM_PTR(&mp_type_AttributeError) }, + { MP_ROM_QSTR(MP_QSTR_EOFError), MP_ROM_PTR(&mp_type_EOFError) }, + { MP_ROM_QSTR(MP_QSTR_Exception), MP_ROM_PTR(&mp_type_Exception) }, + { MP_ROM_QSTR(MP_QSTR_GeneratorExit), MP_ROM_PTR(&mp_type_GeneratorExit) }, + { MP_ROM_QSTR(MP_QSTR_ImportError), MP_ROM_PTR(&mp_type_ImportError) }, + { MP_ROM_QSTR(MP_QSTR_IndentationError), MP_ROM_PTR(&mp_type_IndentationError) }, + { MP_ROM_QSTR(MP_QSTR_IndexError), MP_ROM_PTR(&mp_type_IndexError) }, + { MP_ROM_QSTR(MP_QSTR_KeyboardInterrupt), MP_ROM_PTR(&mp_type_KeyboardInterrupt) }, + { MP_ROM_QSTR(MP_QSTR_KeyError), MP_ROM_PTR(&mp_type_KeyError) }, + { MP_ROM_QSTR(MP_QSTR_LookupError), MP_ROM_PTR(&mp_type_LookupError) }, + { MP_ROM_QSTR(MP_QSTR_MemoryError), MP_ROM_PTR(&mp_type_MemoryError) }, + { MP_ROM_QSTR(MP_QSTR_NameError), MP_ROM_PTR(&mp_type_NameError) }, + { MP_ROM_QSTR(MP_QSTR_NotImplementedError), MP_ROM_PTR(&mp_type_NotImplementedError) }, + { MP_ROM_QSTR(MP_QSTR_OSError), MP_ROM_PTR(&mp_type_OSError) }, + { MP_ROM_QSTR(MP_QSTR_OverflowError), MP_ROM_PTR(&mp_type_OverflowError) }, + { MP_ROM_QSTR(MP_QSTR_RuntimeError), MP_ROM_PTR(&mp_type_RuntimeError) }, + #if MICROPY_PY_ASYNC_AWAIT + { MP_ROM_QSTR(MP_QSTR_StopAsyncIteration), MP_ROM_PTR(&mp_type_StopAsyncIteration) }, + #endif + { MP_ROM_QSTR(MP_QSTR_StopIteration), MP_ROM_PTR(&mp_type_StopIteration) }, + { MP_ROM_QSTR(MP_QSTR_SyntaxError), MP_ROM_PTR(&mp_type_SyntaxError) }, + { MP_ROM_QSTR(MP_QSTR_SystemExit), MP_ROM_PTR(&mp_type_SystemExit) }, + { MP_ROM_QSTR(MP_QSTR_TypeError), MP_ROM_PTR(&mp_type_TypeError) }, + #if MICROPY_PY_BUILTINS_STR_UNICODE + { MP_ROM_QSTR(MP_QSTR_UnicodeError), MP_ROM_PTR(&mp_type_UnicodeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ValueError), MP_ROM_PTR(&mp_type_ValueError) }, + #if MICROPY_EMIT_NATIVE + { MP_ROM_QSTR(MP_QSTR_ViperTypeError), MP_ROM_PTR(&mp_type_ViperTypeError) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ZeroDivisionError), MP_ROM_PTR(&mp_type_ZeroDivisionError) }, + + // Extra builtins as defined by a port + MICROPY_PORT_BUILTINS +}; + +MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table); + +const mp_obj_module_t mp_module_builtins = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_builtins_globals, +}; diff --git a/ports/wapy/objarray_no_nlr.c b/ports/wapy/objarray_no_nlr.c new file mode 100644 index 000000000..c3057248a --- /dev/null +++ b/ports/wapy/objarray_no_nlr.c @@ -0,0 +1,715 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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 "py/runtime.h" +#include "py/binary.h" +#include "py/objstr.h" +#include "py/objarray.h" + +#if MICROPY_PY_ARRAY || MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_BUILTINS_MEMORYVIEW + +// About memoryview object: We want to reuse as much code as possible from +// array, and keep the memoryview object 4 words in size so it fits in 1 GC +// block. Also, memoryview must keep a pointer to the base of the buffer so +// that the buffer is not GC'd if the original parent object is no longer +// around (we are assuming that all memoryview'able objects return a pointer +// which points to the start of a GC chunk). Given the above constraints we +// do the following: +// - typecode high bit is set if the buffer is read-write (else read-only) +// - free is the offset in elements to the first item in the memoryview +// - len is the length in elements +// - items points to the start of the original buffer +// Note that we don't handle the case where the original buffer might change +// size due to a resize of the original parent object. + +#if MICROPY_PY_BUILTINS_MEMORYVIEW +#define TYPECODE_MASK (0x7f) +#define memview_offset free +#else +// make (& TYPECODE_MASK) a null operation if memorview not enabled +#define TYPECODE_MASK (~(size_t)0) +// memview_offset should not be accessed if memoryview is not enabled, +// so not defined to catch errors +#endif + +STATIC mp_obj_t array_iterator_new(mp_obj_t array_in, mp_obj_iter_buf_t *iter_buf); +STATIC mp_obj_t array_append(mp_obj_t self_in, mp_obj_t arg); +STATIC mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in); +STATIC mp_int_t array_get_buffer(mp_obj_t o_in, mp_buffer_info_t *bufinfo, mp_uint_t flags); + +/******************************************************************************/ +// array + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC void array_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); + if (o->typecode == BYTEARRAY_TYPECODE) { + mp_print_str(print, "bytearray(b"); + mp_str_print_quoted(print, o->items, o->len, true); + } else { + mp_printf(print, "array('%c'", o->typecode); + if (o->len > 0) { + mp_print_str(print, ", ["); + for (size_t i = 0; i < o->len; i++) { + if (i > 0) { + mp_print_str(print, ", "); + } + mp_obj_print_helper(print, mp_binary_get_val_array(o->typecode, o->items, i), PRINT_REPR); + } + mp_print_str(print, "]"); + } + } + mp_print_str(print, ")"); +} +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC mp_obj_array_t *array_new(char typecode, size_t n) { + int typecode_size = mp_binary_get_size('@', typecode, NULL); + if (typecode_size == 0) { + return NULL; + } + mp_obj_array_t *o = m_new_obj(mp_obj_array_t); + if (o == NULL) { + return NULL; + } + #if MICROPY_PY_BUILTINS_BYTEARRAY && MICROPY_PY_ARRAY + o->base.type = (typecode == BYTEARRAY_TYPECODE) ? &mp_type_bytearray : &mp_type_array; + #elif MICROPY_PY_BUILTINS_BYTEARRAY + o->base.type = &mp_type_bytearray; + #else + o->base.type = &mp_type_array; + #endif + o->typecode = typecode; + o->free = 0; + o->len = n; + o->items = m_new(byte, typecode_size * o->len); + return o; +} +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +#include +STATIC mp_obj_t array_construct(char typecode, mp_obj_t initializer) { + // bytearrays can be raw-initialised from anything with the buffer protocol + // other arrays can only be raw-initialised from bytes and bytearray objects + mp_buffer_info_t bufinfo; + if (((MICROPY_PY_BUILTINS_BYTEARRAY + && typecode == BYTEARRAY_TYPECODE) + || (MICROPY_PY_ARRAY + && (mp_obj_is_type(initializer, &mp_type_bytes) + || (MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(initializer, &mp_type_bytearray))))) + && mp_get_buffer(initializer, &bufinfo, MP_BUFFER_READ)) { + // construct array from raw bytes + // we round-down the len to make it a multiple of sz (CPython raises error) + size_t sz = mp_binary_get_size('@', typecode, NULL); + size_t len = bufinfo.len / sz; + mp_obj_array_t *o = array_new(typecode, len); + if (o == NULL) { + return MP_OBJ_NULL; + } + memcpy(o->items, bufinfo.buf, len * sz); + return MP_OBJ_FROM_PTR(o); + } + + size_t len; + // Try to create array of exact len if initializer len is known + mp_obj_t len_in = mp_obj_len_maybe(initializer); + if (len_in == MP_OBJ_NULL) { + len = 0; + } else { + len = MP_OBJ_SMALL_INT_VALUE(len_in); + } + + mp_obj_array_t *array = array_new(typecode, len); + if (array == NULL) { + return MP_OBJ_NULL; + } + + mp_obj_t iterable = mp_getiter(initializer, NULL); + mp_obj_t item; + size_t i = 0; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + if (len == 0) { + array_append(MP_OBJ_FROM_PTR(array), item); + } else { + mp_binary_set_val_array(typecode, array->items, i++, item); + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + + return MP_OBJ_FROM_PTR(array); +} +#endif + +#if MICROPY_PY_ARRAY +STATIC mp_obj_t array_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + mp_arg_check_num(n_args, n_kw, 1, 2, false); + + // get typecode + const char *typecode = mp_obj_str_get_str(args[0]); + + if (n_args == 1) { + // 1 arg: make an empty array + return MP_OBJ_FROM_PTR(array_new(*typecode, 0)); + } else { + // 2 args: construct the array from the given object + return array_construct(*typecode, args[1]); + } +} +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY +STATIC mp_obj_t bytearray_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + // Can take 2nd/3rd arg if constructs from str + mp_arg_check_num(n_args, n_kw, 0, 3, false); + + if (n_args == 0) { + // no args: construct an empty bytearray + return MP_OBJ_FROM_PTR(array_new(BYTEARRAY_TYPECODE, 0)); + } else if (mp_obj_is_int(args[0])) { + // 1 arg, an integer: construct a blank bytearray of that length + mp_uint_t len = mp_obj_get_int(args[0]); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + mp_obj_array_t *o = array_new(BYTEARRAY_TYPECODE, len); + if (o == NULL) { + return MP_OBJ_NULL; + } + memset(o->items, 0, len); + return MP_OBJ_FROM_PTR(o); + } else { + // 1 arg: construct the bytearray from that + return array_construct(BYTEARRAY_TYPECODE, args[0]); + } +} +#endif + +#if MICROPY_PY_BUILTINS_MEMORYVIEW + +mp_obj_t mp_obj_new_memoryview(byte typecode, size_t nitems, void *items) { + mp_obj_array_t *self = m_new_obj(mp_obj_array_t); + if (self == NULL) { + return MP_OBJ_NULL; + } + self->base.type = &mp_type_memoryview; + self->typecode = typecode; + self->memview_offset = 0; + self->len = nitems; + self->items = items; + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t memoryview_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + + // TODO possibly allow memoryview constructor to take start/stop so that one + // can do memoryview(b, 4, 8) instead of memoryview(b)[4:8] (uses less RAM) + + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); + + mp_obj_array_t *self = MP_OBJ_TO_PTR(mp_obj_new_memoryview(bufinfo.typecode, + bufinfo.len / mp_binary_get_size('@', bufinfo.typecode, NULL), + bufinfo.buf)); + + // test if the object can be written to + if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) { + self->typecode |= MP_OBJ_ARRAY_TYPECODE_FLAG_RW; // indicate writable buffer + } + + return MP_OBJ_FROM_PTR(self); +} + +#if MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE +STATIC void memoryview_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + return; + } + if (attr == MP_QSTR_itemsize) { + mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_binary_get_size('@', self->typecode & TYPECODE_MASK, NULL)); + } +} +#endif + +#endif + +STATIC mp_obj_t array_unary_op(mp_unary_op_t op, mp_obj_t o_in) { + mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(o->len != 0); + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(o->len); + default: + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t array_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + mp_obj_array_t *lhs = MP_OBJ_TO_PTR(lhs_in); + switch (op) { + case MP_BINARY_OP_ADD: { + // allow to add anything that has the buffer protocol (extension to CPython) + mp_buffer_info_t lhs_bufinfo; + mp_buffer_info_t rhs_bufinfo; + array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); + if (!mp_get_buffer_raise(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { + return MP_OBJ_NULL; + } + + size_t sz = mp_binary_get_size('@', lhs_bufinfo.typecode, NULL); + + // convert byte count to element count (in case rhs is not multiple of sz) + size_t rhs_len = rhs_bufinfo.len / sz; + + // note: lhs->len is element count of lhs, lhs_bufinfo.len is byte count + mp_obj_array_t *res = array_new(lhs_bufinfo.typecode, lhs->len + rhs_len); + if (res == NULL) { + return MP_OBJ_NULL; + } + mp_seq_cat((byte *)res->items, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_len * sz, byte); + return MP_OBJ_FROM_PTR(res); + } + + case MP_BINARY_OP_INPLACE_ADD: { + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (lhs->base.type == &mp_type_memoryview) { + return MP_OBJ_NULL; // op not supported + } + #endif + if (array_extend(lhs_in, rhs_in) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return lhs_in; + } + + case MP_BINARY_OP_CONTAINS: { + #if MICROPY_PY_BUILTINS_BYTEARRAY + // Can search string only in bytearray + mp_buffer_info_t lhs_bufinfo; + mp_buffer_info_t rhs_bufinfo; + if (mp_get_buffer(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { + if (!mp_obj_is_type(lhs_in, &mp_type_bytearray)) { + return mp_const_false; + } + array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); + return mp_obj_new_bool( + find_subbytes(lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len, 1) != NULL); + } + #endif + + // Otherwise, can only look for a scalar numeric value in an array + if (mp_obj_is_int(rhs_in) || mp_obj_is_float(rhs_in)) { + return mp_raise_NotImplementedError_o(NULL); + } + + return mp_const_false; + } + + case MP_BINARY_OP_EQUAL: { + mp_buffer_info_t lhs_bufinfo; + mp_buffer_info_t rhs_bufinfo; + array_get_buffer(lhs_in, &lhs_bufinfo, MP_BUFFER_READ); + if (!mp_get_buffer(rhs_in, &rhs_bufinfo, MP_BUFFER_READ)) { + return mp_const_false; + } + return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_bufinfo.buf, lhs_bufinfo.len, rhs_bufinfo.buf, rhs_bufinfo.len)); + } + + default: + return MP_OBJ_NULL; // op not supported + } +} + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC mp_obj_t array_append(mp_obj_t self_in, mp_obj_t arg) { + // self is not a memoryview, so we don't need to use (& TYPECODE_MASK) + assert((MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(self_in, &mp_type_bytearray)) + || (MICROPY_PY_ARRAY && mp_obj_is_type(self_in, &mp_type_array))); + mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->free == 0) { + size_t item_sz = mp_binary_get_size('@', self->typecode, NULL); + // TODO: alloc policy + byte *new_items = m_renew(byte, self->items, item_sz * self->len, item_sz * (self->len + 8)); + if (new_items == NULL) { + return MP_OBJ_NULL; + } + self->free = 8; + self->items = new_items; + mp_seq_clear(self->items, self->len + 1, self->len + self->free, item_sz); + } + mp_binary_set_val_array(self->typecode, self->items, self->len, arg); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + // only update length/free if set succeeded + self->len++; + self->free--; + return mp_const_none; // return None, as per CPython +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(array_append_obj, array_append); + +STATIC mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in) { + // self is not a memoryview, so we don't need to use (& TYPECODE_MASK) + assert((MICROPY_PY_BUILTINS_BYTEARRAY && mp_obj_is_type(self_in, &mp_type_bytearray)) + || (MICROPY_PY_ARRAY && mp_obj_is_type(self_in, &mp_type_array))); + mp_obj_array_t *self = MP_OBJ_TO_PTR(self_in); + + // allow to extend by anything that has the buffer protocol (extension to CPython) + mp_buffer_info_t arg_bufinfo; + mp_get_buffer_raise(arg_in, &arg_bufinfo, MP_BUFFER_READ); + + size_t sz = mp_binary_get_size('@', self->typecode, NULL); + + // convert byte count to element count + size_t len = arg_bufinfo.len / sz; + + // make sure we have enough room to extend + // TODO: alloc policy; at the moment we go conservative + if (self->free < len) { + byte *new_items = m_renew(byte, self->items, (self->len + self->free) * sz, (self->len + len) * sz); + if (new_items == NULL) { + return MP_OBJ_NULL; + } + self->items = new_items; + self->free = 0; + } else { + self->free -= len; + } + + // extend + mp_seq_copy((byte *)self->items + self->len * sz, arg_bufinfo.buf, len * sz, byte); + self->len += len; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(array_extend_obj, array_extend); +#endif + +STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete item + // TODO implement + // TODO: confirmed that both bytearray and array.array support + // slice deletion + return MP_OBJ_NULL; // op not supported + } else { + mp_obj_array_t *o = MP_OBJ_TO_PTR(self_in); + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index_in, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(o->len, index_in, &slice)) { + return mp_raise_NotImplementedError_o("only slices with step=1 (aka None) are supported"); + } + if (value != MP_OBJ_SENTINEL) { + #if MICROPY_PY_ARRAY_SLICE_ASSIGN + // Assign + size_t src_len; + void *src_items; + size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); + if (mp_obj_is_obj(value) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(value))->type->subscr == array_subscr) { + // value is array, bytearray or memoryview + mp_obj_array_t *src_slice = MP_OBJ_TO_PTR(value); + if (item_sz != mp_binary_get_size('@', src_slice->typecode & TYPECODE_MASK, NULL)) { + compat_error: + return mp_raise_ValueError_o("lhs and rhs should be compatible"); + } + src_len = src_slice->len; + src_items = src_slice->items; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (mp_obj_is_type(value, &mp_type_memoryview)) { + src_items = (uint8_t *)src_items + (src_slice->memview_offset * item_sz); + } + #endif + } else if (mp_obj_is_type(value, &mp_type_bytes)) { + if (item_sz != 1) { + goto compat_error; + } + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(value, &bufinfo, MP_BUFFER_READ); + src_len = bufinfo.len; + src_items = bufinfo.buf; + } else { + return mp_raise_NotImplementedError_o("array/bytes required on right side"); + } + + // TODO: check src/dst compat + mp_int_t len_adj = src_len - (slice.stop - slice.start); + uint8_t *dest_items = o->items; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + if (!(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW)) { + // store to read-only memoryview not allowed + return MP_OBJ_NULL; + } + if (len_adj != 0) { + goto compat_error; + } + dest_items += o->memview_offset * item_sz; + } + #endif + if (len_adj > 0) { + if ((unsigned long)len_adj > o->free) { + // TODO: alloc policy; at the moment we go conservative + dest_items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz); + if (dest_items == NULL) { + return MP_OBJ_NULL; + } + o->items = dest_items; + o->free = len_adj; + } + mp_seq_replace_slice_grow_inplace(dest_items, o->len, + slice.start, slice.stop, src_items, src_len, len_adj, item_sz); + } else { + mp_seq_replace_slice_no_grow(dest_items, o->len, + slice.start, slice.stop, src_items, src_len, item_sz); + // Clear "freed" elements at the end of list + // TODO: This is actually only needed for typecode=='O' + mp_seq_clear(dest_items, o->len + len_adj, o->len, item_sz); + // TODO: alloc policy after shrinking + } + o->free -= len_adj; + o->len += len_adj; + return mp_const_none; + #else + return MP_OBJ_NULL; // op not supported + #endif + } + + mp_obj_array_t *res; + size_t sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); + assert(sz > 0); + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + res = m_new_obj(mp_obj_array_t); + if (res == NULL) { + return MP_OBJ_NULL; + } + *res = *o; + res->memview_offset += slice.start; + res->len = slice.stop - slice.start; + } else + #endif + { + res = array_new(o->typecode, slice.stop - slice.start); + if (res == NULL) { + return MP_OBJ_NULL; + } + memcpy(res->items, (uint8_t *)o->items + slice.start * sz, (slice.stop - slice.start) * sz); + } + return MP_OBJ_FROM_PTR(res); + } else +#endif + { + size_t index = mp_get_index(o->base.type, o->len, index_in, false); + if (index == (size_t)-1) { + return MP_OBJ_NULL; + } + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + index += o->memview_offset; + if (value != MP_OBJ_SENTINEL && !(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW)) { + // store to read-only memoryview + return MP_OBJ_NULL; + } + } + #endif + if (value == MP_OBJ_SENTINEL) { + // load + return mp_binary_get_val_array(o->typecode & TYPECODE_MASK, o->items, index); + } else { + // store + mp_binary_set_val_array(o->typecode & TYPECODE_MASK, o->items, index, value); + return mp_const_none; + } + } + } +} + +STATIC mp_int_t array_get_buffer(mp_obj_t o_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + mp_obj_array_t *o = MP_OBJ_TO_PTR(o_in); + size_t sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL); + bufinfo->buf = o->items; + bufinfo->len = o->len * sz; + bufinfo->typecode = o->typecode & TYPECODE_MASK; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (o->base.type == &mp_type_memoryview) { + if (!(o->typecode & MP_OBJ_ARRAY_TYPECODE_FLAG_RW) && (flags & MP_BUFFER_WRITE)) { + // read-only memoryview + return 1; + } + bufinfo->buf = (uint8_t *)bufinfo->buf + (size_t)o->memview_offset * sz; + } + #else + (void)flags; + #endif + return 0; +} + +#if MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_ARRAY +STATIC const mp_rom_map_elem_t array_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&array_append_obj) }, + { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&array_extend_obj) }, + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&bytes_decode_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(array_locals_dict, array_locals_dict_table); +#endif + +#if MICROPY_PY_ARRAY +const mp_obj_type_t mp_type_array = { + { &mp_type_type }, + .name = MP_QSTR_array, + .print = array_print, + .make_new = array_make_new, + .getiter = array_iterator_new, + .unary_op = array_unary_op, + .binary_op = array_binary_op, + .subscr = array_subscr, + .buffer_p = { .get_buffer = array_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&array_locals_dict, +}; +#endif + +#if MICROPY_PY_BUILTINS_BYTEARRAY +const mp_obj_type_t mp_type_bytearray = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, + .name = MP_QSTR_bytearray, + .print = array_print, + .make_new = bytearray_make_new, + .getiter = array_iterator_new, + .unary_op = array_unary_op, + .binary_op = array_binary_op, + .subscr = array_subscr, + .buffer_p = { .get_buffer = array_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&array_locals_dict, +}; +#endif + +#if MICROPY_PY_BUILTINS_MEMORYVIEW +const mp_obj_type_t mp_type_memoryview = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE, + .name = MP_QSTR_memoryview, + .make_new = memoryview_make_new, + .getiter = array_iterator_new, + .unary_op = array_unary_op, + .binary_op = array_binary_op, + #if MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE + .attr = memoryview_attr, + #endif + .subscr = array_subscr, + .buffer_p = { .get_buffer = array_get_buffer }, +}; +#endif + +/* unused +size_t mp_obj_array_len(mp_obj_t self_in) { + return ((mp_obj_array_t *)self_in)->len; +} +*/ + +#if MICROPY_PY_BUILTINS_BYTEARRAY +mp_obj_t mp_obj_new_bytearray(size_t n, void *items) { + mp_obj_array_t *o = array_new(BYTEARRAY_TYPECODE, n); + memcpy(o->items, items, n); + return MP_OBJ_FROM_PTR(o); +} + +// Create bytearray which references specified memory area +mp_obj_t mp_obj_new_bytearray_by_ref(size_t n, void *items) { + mp_obj_array_t *o = m_new_obj(mp_obj_array_t); + o->base.type = &mp_type_bytearray; + o->typecode = BYTEARRAY_TYPECODE; + o->free = 0; + o->len = n; + o->items = items; + return MP_OBJ_FROM_PTR(o); +} +#endif + +/******************************************************************************/ +// array iterator + +typedef struct _mp_obj_array_it_t { + mp_obj_base_t base; + mp_obj_array_t *array; + size_t offset; + size_t cur; +} mp_obj_array_it_t; + +STATIC mp_obj_t array_it_iternext(mp_obj_t self_in) { + mp_obj_array_it_t *self = MP_OBJ_TO_PTR(self_in); + if (self->cur < self->array->len) { + return mp_binary_get_val_array(self->array->typecode & TYPECODE_MASK, self->array->items, self->offset + self->cur++); + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +STATIC const mp_obj_type_t array_it_type = { + { &mp_type_type }, + .name = MP_QSTR_iterator, + .getiter = mp_identity_getiter, + .iternext = array_it_iternext, +}; + +STATIC mp_obj_t array_iterator_new(mp_obj_t array_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_array_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_array_t *array = MP_OBJ_TO_PTR(array_in); + mp_obj_array_it_t *o = (mp_obj_array_it_t *)iter_buf; + o->base.type = &array_it_type; + o->array = array; + o->offset = 0; + o->cur = 0; + #if MICROPY_PY_BUILTINS_MEMORYVIEW + if (array->base.type == &mp_type_memoryview) { + o->offset = array->memview_offset; + } + #endif + return MP_OBJ_FROM_PTR(o); +} + +#endif // MICROPY_PY_ARRAY || MICROPY_PY_BUILTINS_BYTEARRAY || MICROPY_PY_BUILTINS_MEMORYVIEW diff --git a/ports/wapy/objdict_no_nlr.c b/ports/wapy/objdict_no_nlr.c new file mode 100644 index 000000000..97a7ba2cc --- /dev/null +++ b/ports/wapy/objdict_no_nlr.c @@ -0,0 +1,672 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2017 Paul Sokolovsky + * + * 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/builtin.h" +#include "py/objtype.h" +#include "py/objstr.h" + +#define mp_obj_is_dict_type(o) (mp_obj_is_obj(o) && ((mp_obj_base_t *)MP_OBJ_TO_PTR(o))->type->make_new == mp_obj_dict_make_new) + +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 +// the iteration is held in *cur and should be initialised with zero for the +// first call. Will return NULL when no more elements are available. +STATIC mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, size_t *cur) { + size_t max = dict->map.alloc; + mp_map_t *map = &dict->map; + + size_t i = *cur; + for (; i < max; i++) { + if (mp_map_slot_is_filled(map, i)) { + *cur = i + 1; + return &(map->table[i]); + } + } + + assert(map->used == 0 || i == max); + return NULL; +} + +STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + bool first = true; + if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { + kind = PRINT_REPR; + } + if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { + mp_printf(print, "%q(", self->base.type->name); + } + mp_print_str(print, "{"); + size_t cur = 0; + mp_map_elem_t *next = NULL; + while ((next = dict_iter_next(self, &cur)) != NULL) { + if (!first) { + mp_print_str(print, ", "); + } + first = false; + bool add_quote = MICROPY_PY_UJSON && kind == PRINT_JSON && !mp_obj_is_str_or_bytes(next->key); + if (add_quote) { + mp_print_str(print, "\""); + } + mp_obj_print_helper(print, next->key, kind); + if (add_quote) { + mp_print_str(print, "\""); + } + mp_print_str(print, ": "); + mp_obj_print_helper(print, next->value, kind); + } + mp_print_str(print, "}"); + if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { + mp_print_str(print, ")"); + } +} + +mp_obj_t mp_obj_dict_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_t dict_out = mp_obj_new_dict(0); + mp_obj_dict_t *dict = MP_OBJ_TO_PTR(dict_out); + dict->base.type = type; + #if MICROPY_PY_COLLECTIONS_ORDEREDDICT + if (type == &mp_type_ordereddict) { + dict->map.is_ordered = 1; + } + #endif + if (n_args > 0 || n_kw > 0) { + mp_obj_t args2[2] = {dict_out, args[0]}; // args[0] is always valid, even if it's not a positional arg + mp_map_t kwargs; + mp_map_init_fixed_table(&kwargs, n_kw, args + n_args); + if (dict_update(n_args + 1, args2, &kwargs) == MP_OBJ_NULL) { // dict_update will check that n_args + 1 == 1 or 2 + return MP_OBJ_NULL; + } + } + return dict_out; +} + +STATIC mp_obj_t dict_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(self->map.used != 0); + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(self->map.used); + #if MICROPY_PY_SYS_GETSIZEOF + case MP_UNARY_OP_SIZEOF: { + size_t sz = sizeof(*self) + sizeof(*self->map.table) * self->map.alloc; + return MP_OBJ_NEW_SMALL_INT(sz); + } + #endif + default: + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t dict_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + mp_obj_dict_t *o = MP_OBJ_TO_PTR(lhs_in); + switch (op) { + case MP_BINARY_OP_CONTAINS: { + mp_map_elem_t *elem = mp_map_lookup(&o->map, rhs_in, MP_MAP_LOOKUP); + return mp_obj_new_bool(elem != NULL); + } + case MP_BINARY_OP_EQUAL: { + #if MICROPY_PY_COLLECTIONS_ORDEREDDICT + if (MP_UNLIKELY(mp_obj_is_type(lhs_in, &mp_type_ordereddict) && mp_obj_is_type(rhs_in, &mp_type_ordereddict))) { + // Iterate through both dictionaries simultaneously and compare keys and values. + mp_obj_dict_t *rhs = MP_OBJ_TO_PTR(rhs_in); + size_t c1 = 0, c2 = 0; + mp_map_elem_t *e1 = dict_iter_next(o, &c1), *e2 = dict_iter_next(rhs, &c2); + for (; e1 != NULL && e2 != NULL; e1 = dict_iter_next(o, &c1), e2 = dict_iter_next(rhs, &c2)) { + if (!mp_obj_equal(e1->key, e2->key) || !mp_obj_equal(e1->value, e2->value)) { + return mp_const_false; + } + } + return e1 == NULL && e2 == NULL ? mp_const_true : mp_const_false; + } + #endif + + if (mp_obj_is_type(rhs_in, &mp_type_dict)) { + mp_obj_dict_t *rhs = MP_OBJ_TO_PTR(rhs_in); + if (o->map.used != rhs->map.used) { + return mp_const_false; + } + + size_t cur = 0; + mp_map_elem_t *next = NULL; + while ((next = dict_iter_next(o, &cur)) != NULL) { + mp_map_elem_t *elem = mp_map_lookup(&rhs->map, next->key, MP_MAP_LOOKUP); + if (elem == NULL || !mp_obj_equal(next->value, elem->value)) { + return mp_const_false; + } + } + return mp_const_true; + } else { + // dict is not equal to instance of any other type + return mp_const_false; + } + } + default: + // op not supported + return MP_OBJ_NULL; + } +} + +// Note: Make sure this is inlined in load part of dict_subscr() below. +mp_obj_t mp_obj_dict_get(mp_obj_t self_in, mp_obj_t index) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_elem_t *elem = mp_map_lookup(&self->map, index, MP_MAP_LOOKUP); + if (elem == NULL) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, index)); + } else { + return elem->value; + } +} + +STATIC mp_obj_t dict_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete + if (mp_obj_dict_delete(self_in, index) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_const_none; + } else if (value == MP_OBJ_SENTINEL) { + // load + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_elem_t *elem = mp_map_lookup(&self->map, index, MP_MAP_LOOKUP); + if (elem == NULL) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, index)); + } else { + return elem->value; + } + } else { + // store + if (mp_obj_dict_store(self_in, index, value) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return mp_const_none; + } +} + +/******************************************************************************/ +/* dict methods */ + +STATIC int mp_ensure_not_fixed(const mp_obj_dict_t *dict) { + if (dict->map.is_fixed) { + mp_raise_TypeError_o(NULL); + return 1; + } + return 0; +} + +STATIC mp_obj_t dict_clear(mp_obj_t self_in) { + mp_check_self(mp_obj_is_dict_type(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + + mp_map_clear(&self->map); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_clear_obj, dict_clear); + +mp_obj_t mp_obj_dict_copy(mp_obj_t self_in) { + mp_check_self(mp_obj_is_dict_type(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t other_out = mp_obj_new_dict(self->map.alloc); + mp_obj_dict_t *other = MP_OBJ_TO_PTR(other_out); + other->base.type = self->base.type; + other->map.used = self->map.used; + other->map.all_keys_are_qstrs = self->map.all_keys_are_qstrs; + other->map.is_fixed = 0; + other->map.is_ordered = self->map.is_ordered; + memcpy(other->map.table, self->map.table, self->map.alloc * sizeof(mp_map_elem_t)); + return other_out; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_copy_obj, mp_obj_dict_copy); + +#if MICROPY_PY_BUILTINS_DICT_FROMKEYS +// this is a classmethod +STATIC mp_obj_t dict_fromkeys(size_t n_args, const mp_obj_t *args) { + mp_obj_t iter = mp_getiter(args[1], NULL); + mp_obj_t value = mp_const_none; + mp_obj_t next = MP_OBJ_NULL; + + if (n_args > 2) { + value = args[2]; + } + + // optimisation to allocate result based on len of argument + mp_obj_t self_out; + mp_obj_t len = mp_obj_len_maybe(args[1]); + if (len == MP_OBJ_NULL) { + /* object's type doesn't have a __len__ slot */ + self_out = mp_obj_new_dict(0); + } else { + self_out = mp_obj_new_dict(MP_OBJ_SMALL_INT_VALUE(len)); + } + + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_out); + while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + mp_map_lookup(&self->map, next, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + } + + return self_out; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_fromkeys_fun_obj, 2, 3, dict_fromkeys); +STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(dict_fromkeys_obj, MP_ROM_PTR(&dict_fromkeys_fun_obj)); +#endif + +STATIC mp_obj_t dict_get_helper(size_t n_args, const mp_obj_t *args, mp_map_lookup_kind_t lookup_kind) { + mp_check_self(mp_obj_is_dict_or_ordereddict(args[0])); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); + if (lookup_kind != MP_MAP_LOOKUP) { + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + } + mp_map_elem_t *elem = mp_map_lookup(&self->map, args[1], lookup_kind); + mp_obj_t value; + if (elem == NULL || elem->value == MP_OBJ_NULL) { + if (n_args == 2) { + if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, args[1])); + } else { + value = mp_const_none; + } + } else { + value = args[2]; + } + if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { + elem->value = value; + } + } else { + value = elem->value; + if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) { + elem->value = MP_OBJ_NULL; // so that GC can collect the deleted value + } + } + return value; +} + +STATIC mp_obj_t dict_get(size_t n_args, const mp_obj_t *args) { + return dict_get_helper(n_args, args, MP_MAP_LOOKUP); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_get_obj, 2, 3, dict_get); + +STATIC mp_obj_t dict_pop(size_t n_args, const mp_obj_t *args) { + return dict_get_helper(n_args, args, MP_MAP_LOOKUP_REMOVE_IF_FOUND); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_pop_obj, 2, 3, dict_pop); + +STATIC mp_obj_t dict_setdefault(size_t n_args, const mp_obj_t *args) { + return dict_get_helper(n_args, args, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(dict_setdefault_obj, 2, 3, dict_setdefault); + +STATIC mp_obj_t dict_popitem(mp_obj_t self_in) { + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + if (self->map.used == 0) { + mp_raise_msg(&mp_type_KeyError, MP_ERROR_TEXT("popitem(): dictionary is empty")); + } + size_t cur = 0; + #if MICROPY_PY_COLLECTIONS_ORDEREDDICT + if (self->map.is_ordered) { + cur = self->map.used - 1; + } + #endif + mp_map_elem_t *next = dict_iter_next(self, &cur); + assert(next); + self->map.used--; + mp_obj_t items[] = {next->key, next->value}; + next->key = MP_OBJ_SENTINEL; // must mark key as sentinel to indicate that it was deleted + next->value = MP_OBJ_NULL; + mp_obj_t tuple = mp_obj_new_tuple(2, items); + + return tuple; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_popitem_obj, dict_popitem); + +STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + mp_check_self(mp_obj_is_dict_or_ordereddict(args[0])); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(args[0]); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + + if (mp_arg_check_num(n_args, kwargs->used, 1, 2, true)) { + return MP_OBJ_NULL; + } + + if (n_args == 2) { + // given a positional argument + + if (mp_obj_is_dict_or_ordereddict(args[1])) { + // update from other dictionary (make sure other is not self) + if (args[1] != args[0]) { + size_t cur = 0; + mp_map_elem_t *elem = NULL; + while ((elem = dict_iter_next((mp_obj_dict_t *)MP_OBJ_TO_PTR(args[1]), &cur)) != NULL) { + mp_map_lookup(&self->map, elem->key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = elem->value; + } + } + } else { + // update from a generic iterable of pairs + mp_obj_t iter = mp_getiter(args[1], NULL); + mp_obj_t next = MP_OBJ_NULL; + while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + mp_obj_t inneriter = mp_getiter(next, NULL); + mp_obj_t key = mp_iternext(inneriter); + mp_obj_t value = mp_iternext(inneriter); + mp_obj_t stop = mp_iternext(inneriter); + if (key == MP_OBJ_STOP_ITERATION + || value == MP_OBJ_STOP_ITERATION + || stop != MP_OBJ_STOP_ITERATION) { + return mp_raise_ValueError_o(MP_ERROR_TEXT("dict update sequence has wrong length")); + } else { + mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value; + } + } + } + } + + // update the dict with any keyword args + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + mp_map_lookup(&self->map, kwargs->table[i].key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = kwargs->table[i].value; + } + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dict_update_obj, 1, dict_update); + + +/******************************************************************************/ +/* dict views */ + +STATIC const mp_obj_type_t dict_view_type; +STATIC const mp_obj_type_t dict_view_it_type; + +typedef enum _mp_dict_view_kind_t { + MP_DICT_VIEW_ITEMS, + MP_DICT_VIEW_KEYS, + MP_DICT_VIEW_VALUES, +} mp_dict_view_kind_t; + +STATIC const char *const mp_dict_view_names[] = {"dict_items", "dict_keys", "dict_values"}; + +typedef struct _mp_obj_dict_view_it_t { + mp_obj_base_t base; + mp_dict_view_kind_t kind; + mp_obj_t dict; + size_t cur; +} mp_obj_dict_view_it_t; + +typedef struct _mp_obj_dict_view_t { + mp_obj_base_t base; + mp_obj_t dict; + mp_dict_view_kind_t kind; +} mp_obj_dict_view_t; + +STATIC mp_obj_t dict_view_it_iternext(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &dict_view_it_type)); + mp_obj_dict_view_it_t *self = MP_OBJ_TO_PTR(self_in); + mp_map_elem_t *next = dict_iter_next(MP_OBJ_TO_PTR(self->dict), &self->cur); + + if (next == NULL) { + return MP_OBJ_STOP_ITERATION; + } else { + switch (self->kind) { + case MP_DICT_VIEW_ITEMS: + default: { + mp_obj_t items[] = {next->key, next->value}; + return mp_obj_new_tuple(2, items); + } + case MP_DICT_VIEW_KEYS: + return next->key; + case MP_DICT_VIEW_VALUES: + return next->value; + } + } +} + +STATIC const mp_obj_type_t dict_view_it_type = { + { &mp_type_type }, + .name = MP_QSTR_iterator, + .getiter = mp_identity_getiter, + .iternext = dict_view_it_iternext, +}; + +STATIC mp_obj_t dict_view_getiter(mp_obj_t view_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_dict_view_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_check_self(mp_obj_is_type(view_in, &dict_view_type)); + mp_obj_dict_view_t *view = MP_OBJ_TO_PTR(view_in); + mp_obj_dict_view_it_t *o = (mp_obj_dict_view_it_t *)iter_buf; + o->base.type = &dict_view_it_type; + o->kind = view->kind; + o->dict = view->dict; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} + +STATIC void dict_view_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_check_self(mp_obj_is_type(self_in, &dict_view_type)); + mp_obj_dict_view_t *self = MP_OBJ_TO_PTR(self_in); + bool first = true; + mp_print_str(print, mp_dict_view_names[self->kind]); + mp_print_str(print, "(["); + mp_obj_iter_buf_t iter_buf; + mp_obj_t self_iter = dict_view_getiter(self_in, &iter_buf); + mp_obj_t next = MP_OBJ_NULL; + while ((next = dict_view_it_iternext(self_iter)) != MP_OBJ_STOP_ITERATION) { + if (!first) { + mp_print_str(print, ", "); + } + first = false; + mp_obj_print_helper(print, next, PRINT_REPR); + } + mp_print_str(print, "])"); +} + +STATIC mp_obj_t dict_view_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + // only supported for the 'keys' kind until sets and dicts are refactored + mp_obj_dict_view_t *o = MP_OBJ_TO_PTR(lhs_in); + if (o->kind != MP_DICT_VIEW_KEYS) { + return MP_OBJ_NULL; // op not supported + } + if (op != MP_BINARY_OP_CONTAINS) { + return MP_OBJ_NULL; // op not supported + } + return dict_binary_op(op, o->dict, rhs_in); +} + +STATIC const mp_obj_type_t dict_view_type = { + { &mp_type_type }, + .name = MP_QSTR_dict_view, + .print = dict_view_print, + .binary_op = dict_view_binary_op, + .getiter = dict_view_getiter, +}; + +STATIC mp_obj_t mp_obj_new_dict_view(mp_obj_t dict, mp_dict_view_kind_t kind) { + mp_obj_dict_view_t *o = m_new_obj(mp_obj_dict_view_t); + if (o == NULL) { + return MP_OBJ_NULL; + } + o->base.type = &dict_view_type; + o->dict = dict; + o->kind = kind; + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t dict_view(mp_obj_t self_in, mp_dict_view_kind_t kind) { + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); + return mp_obj_new_dict_view(self_in, kind); +} + +STATIC mp_obj_t dict_items(mp_obj_t self_in) { + return dict_view(self_in, MP_DICT_VIEW_ITEMS); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_items_obj, dict_items); + +STATIC mp_obj_t dict_keys(mp_obj_t self_in) { + return dict_view(self_in, MP_DICT_VIEW_KEYS); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_keys_obj, dict_keys); + +STATIC mp_obj_t dict_values(mp_obj_t self_in) { + return dict_view(self_in, MP_DICT_VIEW_VALUES); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(dict_values_obj, dict_values); + +/******************************************************************************/ +/* dict iterator */ + +STATIC mp_obj_t dict_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_dict_view_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); + mp_obj_dict_view_it_t *o = (mp_obj_dict_view_it_t *)iter_buf; + o->base.type = &dict_view_it_type; + o->kind = MP_DICT_VIEW_KEYS; + o->dict = self_in; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} + +/******************************************************************************/ +/* dict constructors & public C API */ + +STATIC const mp_rom_map_elem_t dict_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&dict_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&dict_copy_obj) }, + #if MICROPY_PY_BUILTINS_DICT_FROMKEYS + { MP_ROM_QSTR(MP_QSTR_fromkeys), MP_ROM_PTR(&dict_fromkeys_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&dict_get_obj) }, + { MP_ROM_QSTR(MP_QSTR_items), MP_ROM_PTR(&dict_items_obj) }, + { MP_ROM_QSTR(MP_QSTR_keys), MP_ROM_PTR(&dict_keys_obj) }, + { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&dict_pop_obj) }, + { MP_ROM_QSTR(MP_QSTR_popitem), MP_ROM_PTR(&dict_popitem_obj) }, + { MP_ROM_QSTR(MP_QSTR_setdefault), MP_ROM_PTR(&dict_setdefault_obj) }, + { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&dict_update_obj) }, + { MP_ROM_QSTR(MP_QSTR_values), MP_ROM_PTR(&dict_values_obj) }, + { MP_ROM_QSTR(MP_QSTR___getitem__), MP_ROM_PTR(&mp_op_getitem_obj) }, + { MP_ROM_QSTR(MP_QSTR___setitem__), MP_ROM_PTR(&mp_op_setitem_obj) }, + { MP_ROM_QSTR(MP_QSTR___delitem__), MP_ROM_PTR(&mp_op_delitem_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(dict_locals_dict, dict_locals_dict_table); + +const mp_obj_type_t mp_type_dict = { + { &mp_type_type }, + .name = MP_QSTR_dict, + .print = dict_print, + .make_new = mp_obj_dict_make_new, + .unary_op = dict_unary_op, + .binary_op = dict_binary_op, + .subscr = dict_subscr, + .getiter = dict_getiter, + .locals_dict = (mp_obj_dict_t *)&dict_locals_dict, +}; + +#if MICROPY_PY_COLLECTIONS_ORDEREDDICT +const mp_obj_type_t mp_type_ordereddict = { + { &mp_type_type }, + .name = MP_QSTR_OrderedDict, + .print = dict_print, + .make_new = mp_obj_dict_make_new, + .unary_op = dict_unary_op, + .binary_op = dict_binary_op, + .subscr = dict_subscr, + .getiter = dict_getiter, + .parent = &mp_type_dict, + .locals_dict = (mp_obj_dict_t *)&dict_locals_dict, +}; +#endif + +void mp_obj_dict_init(mp_obj_dict_t *dict, size_t n_args) { + dict->base.type = &mp_type_dict; + mp_map_init(&dict->map, n_args); +} + +mp_obj_t mp_obj_new_dict(size_t n_args) { + mp_obj_dict_t *o = m_new_obj(mp_obj_dict_t); + if (o == NULL) { + return MP_OBJ_NULL; + } + mp_obj_dict_init(o, n_args); + return MP_OBJ_FROM_PTR(o); +} + +size_t mp_obj_dict_len(mp_obj_t self_in) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + return self->map.used; +} + +mp_obj_t mp_obj_dict_store(mp_obj_t self_in, mp_obj_t key, mp_obj_t value) { + mp_check_self(mp_obj_is_dict_or_ordereddict(self_in)); + mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_ensure_not_fixed(self)) { + return MP_OBJ_NULL; + } + mp_map_elem_t *elem = mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + if (elem == NULL) { + // exception + return MP_OBJ_NULL; + } + elem->value = value; + return self_in; +} + +mp_obj_t mp_obj_dict_delete(mp_obj_t self_in, mp_obj_t key) { + mp_obj_t args[2] = {self_in, key}; + if (dict_get_helper(2, args, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + return self_in; +} diff --git a/ports/wapy/objfun_no_nlr.c b/ports/wapy/objfun_no_nlr.c new file mode 100644 index 000000000..d93b7b91b --- /dev/null +++ b/ports/wapy/objfun_no_nlr.c @@ -0,0 +1,680 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014 Paul Sokolovsky + * + * 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/objtuple.h" +#include "py/objfun.h" +#include "py/runtime.h" +#include "py/bc.h" +#include "py/stackctrl.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#else // don't print debugging info +#define DEBUG_PRINT (0) +#define DEBUG_printf(...) (void)0 +#endif + +// Note: the "name" entry in mp_obj_type_t for a function type must be +// MP_QSTR_function because it is used to determine if an object is of generic +// function type. + +/******************************************************************************/ +/* builtin functions */ + +STATIC mp_obj_t fun_builtin_0_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)args; + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_0)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 0, 0, false)) { + return MP_OBJ_NULL; + } + return self->fun._0(); +} + +const mp_obj_type_t mp_type_fun_builtin_0 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_0_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_1_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_1)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 1, 1, false)) { + return MP_OBJ_NULL; + } + return self->fun._1(args[0]); +} + +const mp_obj_type_t mp_type_fun_builtin_1 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_1_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_2_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_2)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 2, 2, false)) { + return MP_OBJ_NULL; + } + return self->fun._2(args[0], args[1]); +} + +const mp_obj_type_t mp_type_fun_builtin_2 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_2_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_3_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_3)); + mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_arg_check_num(n_args, n_kw, 3, 3, false)) { + return MP_OBJ_NULL; + } + return self->fun._3(args[0], args[1], args[2]); +} + +const mp_obj_type_t mp_type_fun_builtin_3 = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_3_call, + .unary_op = mp_generic_unary_op, +}; + +STATIC mp_obj_t fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_var)); + mp_obj_fun_builtin_var_t *self = MP_OBJ_TO_PTR(self_in); + + // check number of arguments + if (mp_arg_check_num_sig(n_args, n_kw, self->sig)) { + return MP_OBJ_NULL; + } + + if (self->sig & 1) { + // function allows keywords + + // we create a map directly from the given args array + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + + return self->fun.kw(n_args, args, &kw_args); + + } else { + // function takes a variable number of arguments, but no keywords + + return self->fun.var(n_args, args); + } +} + +const mp_obj_type_t mp_type_fun_builtin_var = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_builtin_var_call, + .unary_op = mp_generic_unary_op, +}; + +/******************************************************************************/ +/* byte code functions */ + +qstr mp_obj_code_get_name(const byte *code_info) { + MP_BC_PRELUDE_SIZE_DECODE(code_info); + #if MICROPY_PERSISTENT_CODE + return code_info[0] | (code_info[1] << 8); + #else + return mp_decode_uint_value(code_info); + #endif +} + +#if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM +STATIC const mp_obj_type_t mp_type_fun_native; +#endif + +qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) { + const mp_obj_fun_bc_t *fun = MP_OBJ_TO_PTR(fun_in); + #if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM + #else + #error "MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM required" + #endif + if (fun->base.type == &mp_type_fun_native || fun->base.type == &mp_type_native_gen_wrap) { + #pragma message "// TODO native functions don't have name stored" + } else { + + if (fun->base.type == &mp_type_fun_bc ) { + const byte *bc = fun->bytecode; + MP_BC_PRELUDE_SIG_DECODE(bc); + #pragma message "REVIEW: can that one return a NULL ?" + return mp_obj_code_get_name(bc); + } + + #if MICROPY_PY_FUNCTION_ATTRS + +#define STRIP_PREFIX strlen("mp_builtin_") + //PMPP + if ( + (fun->base.type == &mp_type_fun_builtin_0) || + (fun->base.type == &mp_type_fun_builtin_1) || + (fun->base.type == &mp_type_fun_builtin_2) || + (fun->base.type == &mp_type_fun_builtin_3) + ) { + mp_obj_fun_builtin_fixed_t *fn = (mp_obj_fun_builtin_fixed_t *)fun; + return qstr_from_str( &fn->name[STRIP_PREFIX] ); + } + + if ( fun->base.type == &mp_type_fun_builtin_var) { + mp_obj_fun_builtin_var_t *fn = (mp_obj_fun_builtin_var_t *)fun; + return qstr_from_str( &fn->name[STRIP_PREFIX] ); + } + #endif + } + return MP_QSTR_; +} + +#if MICROPY_CPYTHON_COMPAT +STATIC void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_fun_bc_t *o = MP_OBJ_TO_PTR(o_in); + mp_printf(print, "", mp_obj_fun_get_name(o_in), o); +} +#endif + +#if DEBUG_PRINT +STATIC void dump_args(const mp_obj_t *a, size_t sz) { + DEBUG_printf("%p: ", a); + for (size_t i = 0; i < sz; i++) { + DEBUG_printf("%p ", a[i]); + } + DEBUG_printf("\n"); +} +#else +#define dump_args(...) (void)0 +#endif + +// With this macro you can tune the maximum number of function state bytes +// that will be allocated on the stack. Any function that needs more +// than this will try to use the heap, with fallback to stack allocation. +#define VM_MAX_STATE_ON_STACK (sizeof(mp_uint_t) * 11) + +#define DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \ + { \ + const uint8_t *ip = bytecode; \ + size_t n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args; \ + MP_BC_PRELUDE_SIG_DECODE_INTO(ip, n_state_out_var, n_exc_stack, scope_flags, n_pos_args, n_kwonly_args, n_def_args); \ + \ + /* state size in bytes */ \ + state_size_out_var = n_state_out_var *sizeof(mp_obj_t) \ + + n_exc_stack *sizeof(mp_exc_stack_t); \ + } + +static inline mp_obj_t INIT_CODESTATE(mp_code_state_t *code_state, mp_obj_fun_bc_t *_fun_bc, size_t _n_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { + code_state->fun_bc = _fun_bc; \ + code_state->ip = 0; \ + code_state->n_state = _n_state; \ + // TODO can we save old_globals before this call? + mp_obj_t ret = mp_setup_code_state(code_state, n_args, n_kw, args); \ + code_state->old_globals = mp_globals_get(); + return ret; +} + +#if MICROPY_STACKLESS +mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return NULL; + } + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + + size_t n_state, state_size; + DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); + + mp_code_state_t *code_state; + #if MICROPY_ENABLE_PYSTACK + code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); + #else + // If we use m_new_obj_var(), then on no memory, MemoryError will be + // raised. But this is not correct exception for a function call, + // RuntimeError should be raised instead. So, we use m_new_obj_var_maybe(), + // return NULL, then vm.c takes the needed action (either raise + // RuntimeError or fallback to stack allocation). + code_state = m_new_obj_var_maybe(mp_code_state_t, byte, state_size); + if (!code_state) { + return NULL; + } + #endif + + // TODO write test where this fails + if (INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args) == MP_OBJ_NULL) { + #if MICROPY_ENABLE_PYSTACK + mp_nonlocal_free(code_state, sizeof(mp_code_state_t)); + #else + m_del_var(mp_code_state_t, byte, state_size, code_state); + #endif + // exception + return NULL; + } + + // execute the byte code with the correct globals context + mp_globals_set(self->globals); + + return code_state; +} +#endif + + +//STATIC <= NEVER ! +mp_obj_t __attribute__ ((noinline)) fun_bc_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return MP_OBJ_NULL; + } + + DEBUG_printf("Input n_args: " UINT_FMT ", n_kw: " UINT_FMT "\n", n_args, n_kw); + DEBUG_printf("Input pos args: "); + dump_args(args, n_args); + DEBUG_printf("Input kw args: "); + dump_args(args + n_args, n_kw * 2); + + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + + size_t n_state, state_size; + DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); + + // allocate state for locals and stack + mp_code_state_t *code_state = NULL; + #if MICROPY_ENABLE_PYSTACK + code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); + #else + if (state_size > VM_MAX_STATE_ON_STACK) { + code_state = m_new_obj_var_maybe(mp_code_state_t, byte, state_size); + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + if (code_state != NULL) { + memset(code_state->state, 0, state_size); + } + #endif + } + if (code_state == NULL) { + code_state = alloca(sizeof(mp_code_state_t) + state_size); + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + memset(code_state->state, 0, state_size); + #endif + state_size = 0; // indicate that we allocated using alloca + } + #endif + + if (INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args) == MP_OBJ_NULL) { + // exception + #if MICROPY_ENABLE_PYSTACK + mp_pystack_free(code_state); + #else + // free the state if it was allocated on the heap + if (state_size != 0) { + m_del_var(mp_code_state_t, byte, state_size, code_state); + } + #endif + return MP_OBJ_NULL; + } + + // execute the byte code with the correct globals context + mp_globals_set(self->globals); + mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL); + mp_globals_set(code_state->old_globals); + + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + if (code_state->sp < code_state->state) { + mp_printf(MICROPY_DEBUG_PRINTER, "VM stack underflow: " INT_FMT "\n", code_state->sp - code_state->state); + assert(0); + } + } + const byte *bytecode_ptr = self->bytecode; + size_t n_state_unused, n_exc_stack_unused, scope_flags_unused; + size_t n_pos_args, n_kwonly_args, n_def_args_unused; + MP_BC_PRELUDE_SIG_DECODE_INTO(bytecode_ptr, n_state_unused, n_exc_stack_unused, + scope_flags_unused, n_pos_args, n_kwonly_args, n_def_args_unused); + // We can't check the case when an exception is returned in state[0] + // and there are no arguments, because in this case our detection slot may have + // been overwritten by the returned exception (which is allowed). + if (!(vm_return_kind == MP_VM_RETURN_EXCEPTION && n_pos_args + n_kwonly_args == 0)) { + // Just check to see that we have at least 1 null object left in the state. + bool overflow = true; + for (size_t i = 0; i < n_state - n_pos_args - n_kwonly_args; ++i) { + if (code_state->state[i] == MP_OBJ_NULL) { + overflow = false; + break; + } + } + if (overflow) { + mp_printf(MICROPY_DEBUG_PRINTER, "VM stack overflow state=%p n_state+1=" UINT_FMT "\n", code_state->state, n_state); + assert(0); + } + } + #endif + + mp_obj_t result; + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + // return value is in *sp + result = *code_state->sp; + } else { + // must be an exception because normal functions can't yield + assert(vm_return_kind == MP_VM_RETURN_EXCEPTION); + // returned exception is in state[0] + result = code_state->state[0]; + } + + #if MICROPY_ENABLE_PYSTACK + mp_pystack_free(code_state); + #else + // free the state if it was allocated on the heap + if (state_size != 0) { + m_del_var(mp_code_state_t, byte, state_size, code_state); + } + #endif + + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + return result; + } else { // MP_VM_RETURN_EXCEPTION + return mp_raise_o(result); + } +} +/* +void * get_fun_bc_call_addr(){ + return &fun_bc_call; +} +*/ + + +#if MICROPY_PY_FUNCTION_ATTRS +void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] != MP_OBJ_NULL) { + // not load attribute + return; + } + if (attr == MP_QSTR___name__) { + dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(self_in)); + } +} +#endif + +const mp_obj_type_t mp_type_fun_bc = { + { &mp_type_type }, + .name = MP_QSTR_function, + #if MICROPY_CPYTHON_COMPAT + .print = fun_bc_print, + #endif + .call = &fun_bc_call, + .unary_op = mp_generic_unary_op, + #if MICROPY_PY_FUNCTION_ATTRS + .attr = mp_obj_fun_bc_attr, + #endif +}; + +mp_obj_t mp_obj_new_fun_bc(mp_obj_t def_args_in, mp_obj_t def_kw_args, const byte *code, const mp_uint_t *const_table) { + size_t n_def_args = 0; + size_t n_extra_args = 0; + mp_obj_tuple_t *def_args = MP_OBJ_TO_PTR(def_args_in); + if (def_args_in != MP_OBJ_NULL) { + assert(mp_obj_is_type(def_args_in, &mp_type_tuple)); + n_def_args = def_args->len; + n_extra_args = def_args->len; + } + if (def_kw_args != MP_OBJ_NULL) { + n_extra_args += 1; + } + mp_obj_fun_bc_t *o = m_new_obj_var(mp_obj_fun_bc_t, mp_obj_t, n_extra_args); + if (o == NULL) { + return MP_OBJ_NULL; + } + o->base.type = &mp_type_fun_bc; + o->globals = mp_globals_get(); + o->bytecode = code; + o->const_table = const_table; + if (def_args != NULL) { + memcpy(o->extra_args, def_args->items, n_def_args * sizeof(mp_obj_t)); + } + if (def_kw_args != MP_OBJ_NULL) { + o->extra_args[n_def_args] = def_kw_args; + } + return MP_OBJ_FROM_PTR(o); +} + +/******************************************************************************/ +/* native functions */ + +#if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM + +STATIC mp_obj_t fun_native_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return MP_OBJ_NULL; + } + mp_obj_fun_bc_t *self = self_in; + mp_call_fun_t fun = MICROPY_MAKE_POINTER_CALLABLE((void *)self->bytecode); + return fun(self_in, n_args, n_kw, args); +} + +STATIC const mp_obj_type_t mp_type_fun_native = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_native_call, + .unary_op = mp_generic_unary_op, +}; + +mp_obj_t mp_obj_new_fun_native(mp_obj_t def_args_in, mp_obj_t def_kw_args, const void *fun_data, const mp_uint_t *const_table) { + mp_obj_fun_bc_t *o = mp_obj_new_fun_bc(def_args_in, def_kw_args, (const byte *)fun_data, const_table); + o->base.type = &mp_type_fun_native; + return o; +} +#else + #if defined(__EMSCRIPTEN__) + #error "wasm : need MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM for detect native fun" + #endif +#endif // MICROPY_EMIT_NATIVE + +/******************************************************************************/ +/* inline assembler functions */ + +#if MICROPY_EMIT_INLINE_ASM + +typedef struct _mp_obj_fun_asm_t { + mp_obj_base_t base; + size_t n_args; + const void *fun_data; // GC must be able to trace this pointer + mp_uint_t type_sig; +} mp_obj_fun_asm_t; + +typedef mp_uint_t (*inline_asm_fun_0_t)(void); +typedef mp_uint_t (*inline_asm_fun_1_t)(mp_uint_t); +typedef mp_uint_t (*inline_asm_fun_2_t)(mp_uint_t, mp_uint_t); +typedef mp_uint_t (*inline_asm_fun_3_t)(mp_uint_t, mp_uint_t, mp_uint_t); +typedef mp_uint_t (*inline_asm_fun_4_t)(mp_uint_t, mp_uint_t, mp_uint_t, mp_uint_t); + +// convert a MicroPython object to a sensible value for inline asm +STATIC mp_uint_t convert_obj_for_inline_asm(mp_obj_t obj) { + // TODO for byte_array, pass pointer to the array + if (mp_obj_is_small_int(obj)) { + return MP_OBJ_SMALL_INT_VALUE(obj); + } else if (obj == mp_const_none) { + return 0; + } else if (obj == mp_const_false) { + return 0; + } else if (obj == mp_const_true) { + return 1; + } else if (mp_obj_is_type(obj, &mp_type_int)) { + return mp_obj_int_get_truncated(obj); + } else if (mp_obj_is_str(obj)) { + // pointer to the string (it's probably constant though!) + size_t l; + return (mp_uint_t)mp_obj_str_get_data(obj, &l); + } else { + const mp_obj_type_t *type = mp_obj_get_type(obj); + #if MICROPY_PY_BUILTINS_FLOAT + if (type == &mp_type_float) { + // convert float to int (could also pass in float registers) + return (mp_int_t)mp_obj_float_get(obj); + } + #endif + if (type == &mp_type_tuple || type == &mp_type_list) { + // pointer to start of tuple (could pass length, but then could use len(x) for that) + size_t len; + mp_obj_t *items; + mp_obj_get_array(obj, &len, &items); + return (mp_uint_t)items; + } else { + mp_buffer_info_t bufinfo; + if (mp_get_buffer(obj, &bufinfo, MP_BUFFER_READ)) { + // supports the buffer protocol, return a pointer to the data + return (mp_uint_t)bufinfo.buf; + } else { + // just pass along a pointer to the object + return (mp_uint_t)obj; + } + } + } +} + +STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_fun_asm_t *self = self_in; + + mp_arg_check_num(n_args, n_kw, self->n_args, self->n_args, false); + + const void *fun = MICROPY_MAKE_POINTER_CALLABLE(self->fun_data); + + mp_uint_t ret; + if (n_args == 0) { + ret = ((inline_asm_fun_0_t)fun)(); + } else if (n_args == 1) { + ret = ((inline_asm_fun_1_t)fun)(convert_obj_for_inline_asm(args[0])); + } else if (n_args == 2) { + ret = ((inline_asm_fun_2_t)fun)(convert_obj_for_inline_asm(args[0]), convert_obj_for_inline_asm(args[1])); + } else if (n_args == 3) { + ret = ((inline_asm_fun_3_t)fun)(convert_obj_for_inline_asm(args[0]), convert_obj_for_inline_asm(args[1]), convert_obj_for_inline_asm(args[2])); + } else { + // compiler allows at most 4 arguments + assert(n_args == 4); + ret = ((inline_asm_fun_4_t)fun)( + convert_obj_for_inline_asm(args[0]), + convert_obj_for_inline_asm(args[1]), + convert_obj_for_inline_asm(args[2]), + convert_obj_for_inline_asm(args[3]) + ); + } + + return mp_native_to_obj(ret, self->type_sig); +} + +STATIC const mp_obj_type_t mp_type_fun_asm = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = fun_asm_call, + .unary_op = mp_generic_unary_op, +}; + +mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig) { + mp_obj_fun_asm_t *o = m_new_obj(mp_obj_fun_asm_t); + o->base.type = &mp_type_fun_asm; + o->n_args = n_args; + o->fun_data = fun_data; + o->type_sig = type_sig; + return o; +} + +#endif // MICROPY_EMIT_INLINE_ASM + + + + + + + + + + + + + + + +/* + +STATIC mp_obj_t fun_bc_call_pre(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + if (MP_STACK_CHECK()) { + return MP_OBJ_NULL; + } + + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + + size_t n_state, state_size; + DECODE_CODESTATE_SIZE(self->bytecode, n_state, state_size); + + // allocate state for locals and stack + mp_code_state_t *code_state = NULL; + code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + state_size); + + if (INIT_CODESTATE(code_state, self, n_state, n_args, n_kw, args) == MP_OBJ_NULL) { + // exception + mp_pystack_free(code_state); + return MP_OBJ_NULL; + } + + // execute the byte code with the correct globals context + mp_globals_set(self->globals); + return code_state; +} + +// mp_vm_return_kind_t vm_return_kind = mp_execute_bytecode(code_state, MP_OBJ_NULL); + + +STATIC mp_obj_t fun_bc_call_past(mp_code_state_t *code_state, mp_vm_return_kind_t vm_return_kind, mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + + mp_globals_set(code_state->old_globals); + + mp_obj_t result; + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + // return value is in *sp + result = *code_state->sp; + } else { + // must be an exception because normal functions can't yield + assert(vm_return_kind == MP_VM_RETURN_EXCEPTION); + // returned exception is in state[0] + result = code_state->state[0]; + } + + mp_pystack_free(code_state); + + if (vm_return_kind == MP_VM_RETURN_NORMAL) { + return result; + } else { // MP_VM_RETURN_EXCEPTION + return mp_raise_o(result); + } +} + +*/ + diff --git a/ports/wapy/objlist_no_nlr.c b/ports/wapy/objlist_no_nlr.c new file mode 100644 index 000000000..3a45863e5 --- /dev/null +++ b/ports/wapy/objlist_no_nlr.c @@ -0,0 +1,590 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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/objlist.h" +#include "py/runtime.h" +#include "py/stackctrl.h" + +STATIC mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t *iter_buf); +STATIC mp_obj_list_t *list_new(size_t n); +STATIC mp_obj_t list_extend(mp_obj_t self_in, mp_obj_t arg_in); +STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args); + +// TODO: Move to mpconfig.h +#define LIST_MIN_ALLOC 4 + +/******************************************************************************/ +/* list */ + +STATIC void list_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { + mp_obj_list_t *o = MP_OBJ_TO_PTR(o_in); + if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { + kind = PRINT_REPR; + } + mp_print_str(print, "["); + for (size_t i = 0; i < o->len; i++) { + if (i > 0) { + mp_print_str(print, ", "); + } + mp_obj_print_helper(print, o->items[i], kind); + } + mp_print_str(print, "]"); +} + +STATIC mp_obj_t list_extend_from_iter(mp_obj_t list, mp_obj_t iterable) { + mp_obj_t iter = mp_getiter(iterable, NULL); + mp_obj_t item; + while ((item = mp_iternext2(iter)) != MP_OBJ_NULL) { + if (mp_obj_list_append(list, item) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + return list; +} + +STATIC mp_obj_t list_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + mp_arg_check_num(n_args, n_kw, 0, 1, false); + + switch (n_args) { + case 0: + // return a new, empty list + return mp_obj_new_list(0, NULL); + + case 1: + default: { + // make list from iterable + // TODO: optimize list/tuple + mp_obj_t list = mp_obj_new_list(0, NULL); + return list_extend_from_iter(list, args[0]); + } + } +} + +STATIC mp_obj_t list_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(self->len != 0); + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(self->len); + #if MICROPY_PY_SYS_GETSIZEOF + case MP_UNARY_OP_SIZEOF: { + size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc; + return MP_OBJ_NEW_SMALL_INT(sz); + } + #endif + default: + return MP_OBJ_NULL; // op not supported + } +} + +#include +STATIC mp_obj_t list_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { + mp_obj_list_t *o = MP_OBJ_TO_PTR(lhs); + switch (op) { + case MP_BINARY_OP_ADD: { + if (!mp_obj_is_type(rhs, &mp_type_list)) { + return MP_OBJ_NULL; // op not supported + } + mp_obj_list_t *p = MP_OBJ_TO_PTR(rhs); + mp_obj_list_t *s = list_new(o->len + p->len); + mp_seq_cat(s->items, o->items, o->len, p->items, p->len, mp_obj_t); + return MP_OBJ_FROM_PTR(s); + } + case MP_BINARY_OP_INPLACE_ADD: { + list_extend(lhs, rhs); + return lhs; + } + case MP_BINARY_OP_MULTIPLY: { + mp_int_t n; + if (!mp_obj_get_int_maybe(rhs, &n)) { + return MP_OBJ_NULL; // op not supported + } + if (n < 0) { + n = 0; + } + mp_obj_list_t *s = list_new(o->len * n); + if (s == NULL) { + return MP_OBJ_NULL; + } + mp_seq_multiply(o->items, sizeof(*o->items), o->len, n, s->items); + return MP_OBJ_FROM_PTR(s); + } + case MP_BINARY_OP_EQUAL: + case MP_BINARY_OP_LESS: + case MP_BINARY_OP_LESS_EQUAL: + case MP_BINARY_OP_MORE: + case MP_BINARY_OP_MORE_EQUAL: { + if (!mp_obj_is_type(rhs, &mp_type_list)) { + if (op == MP_BINARY_OP_EQUAL) { + return mp_const_false; + } + return MP_OBJ_NULL; // op not supported + } + + mp_obj_list_t *another = MP_OBJ_TO_PTR(rhs); + bool res = mp_seq_cmp_objs(op, o->items, o->len, another->items, another->len); + return mp_obj_new_bool(res); + } + + default: + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t list_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + mp_bound_slice_t slice; + int ret = mp_seq_get_fast_slice_indexes(self->len, index, &slice); + if (ret < 0) { + return MP_OBJ_NULL; + } else if (!ret) { + mp_raise_NotImplementedError(NULL); + } + + mp_int_t len_adj = slice.start - slice.stop; + assert(len_adj <= 0); + mp_seq_replace_slice_no_grow(self->items, self->len, slice.start, slice.stop, self->items /*NULL*/, 0, sizeof(*self->items)); + // Clear "freed" elements at the end of list + mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); + self->len += len_adj; + return mp_const_none; + } + #endif + mp_obj_t args[2] = {self_in, index}; + list_pop(2, args); + return mp_const_none; + } else if (value == MP_OBJ_SENTINEL) { + // load + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_bound_slice_t slice; + int ret = mp_seq_get_fast_slice_indexes(self->len, index, &slice); + if (ret < 0) { + return MP_OBJ_NULL; + } else if (!ret) { + return mp_seq_extract_slice(self->len, self->items, &slice); + } + mp_obj_list_t *res = list_new(slice.stop - slice.start); + if (res == NULL) { + return MP_OBJ_NULL; + } + mp_seq_copy(res->items, self->items + slice.start, res->len, mp_obj_t); + return MP_OBJ_FROM_PTR(res); + } + #endif + size_t index_val = mp_get_index(self->base.type, self->len, index, false); + if (index_val == (size_t)-1) { + // exception + return MP_OBJ_NULL; + } + return self->items[index_val]; + } else { + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + size_t value_len; mp_obj_t *value_items; + if (mp_obj_get_array(value, &value_len, &value_items)) { + return MP_OBJ_NULL; + } + mp_bound_slice_t slice_out; + int ret = mp_seq_get_fast_slice_indexes(self->len, index, &slice_out); + if (ret < 0) { + return MP_OBJ_NULL; + } else if (!ret) { + mp_raise_NotImplementedError(NULL); + } + mp_int_t len_adj = value_len - (slice_out.stop - slice_out.start); + if (len_adj > 0) { + if (self->len + len_adj > self->alloc) { + // TODO: Might optimize memory copies here by checking if block can + // be grown inplace or not + mp_obj_t *new_items = m_renew(mp_obj_t, self->items, self->alloc, self->len + len_adj); + if (new_items == NULL) { + return MP_OBJ_NULL; + } + self->items = new_items; + self->alloc = self->len + len_adj; + } + mp_seq_replace_slice_grow_inplace(self->items, self->len, + slice_out.start, slice_out.stop, value_items, value_len, len_adj, sizeof(*self->items)); + } else { + mp_seq_replace_slice_no_grow(self->items, self->len, + slice_out.start, slice_out.stop, value_items, value_len, sizeof(*self->items)); + // Clear "freed" elements at the end of list + mp_seq_clear(self->items, self->len + len_adj, self->len, sizeof(*self->items)); + // TODO: apply allocation policy re: alloc_size + } + self->len += len_adj; + return mp_const_none; + } + #endif + mp_obj_list_store(self_in, index, value); + return mp_const_none; + } +} + +STATIC mp_obj_t list_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + return mp_obj_new_list_iterator(o_in, 0, iter_buf); +} + +mp_obj_t mp_obj_list_append(mp_obj_t self_in, mp_obj_t arg) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + if (self->len >= self->alloc) { + mp_obj_t *items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc * 2); + if (items == NULL) { + return MP_OBJ_NULL; + } + self->items = items; + self->alloc *= 2; + mp_seq_clear(self->items, self->len + 1, self->alloc, sizeof(*self->items)); + } + self->items[self->len++] = arg; + return mp_const_none; // return None, as per CPython +} + +STATIC mp_obj_t list_extend(mp_obj_t self_in, mp_obj_t arg_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + if (mp_obj_is_type(arg_in, &mp_type_list)) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *arg = MP_OBJ_TO_PTR(arg_in); + + if (self->len + arg->len > self->alloc) { + // TODO: use alloc policy for "4" + mp_obj_t *items = m_renew(mp_obj_t, self->items, self->alloc, self->len + arg->len + 4); + if (items == NULL) { + return MP_OBJ_NULL; + } + self->items = items; + self->alloc = self->len + arg->len + 4; + mp_seq_clear(self->items, self->len + arg->len, self->alloc, sizeof(*self->items)); + } + + memcpy(self->items + self->len, arg->items, sizeof(mp_obj_t) * arg->len); + self->len += arg->len; + } else { + if (list_extend_from_iter(self_in, arg_in) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + return mp_const_none; // return None, as per CPython +} + +STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_type(args[0], &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(args[0]); + if (self->len == 0) { + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("pop from empty list")); + } + size_t index = mp_get_index(self->base.type, self->len, n_args == 1 ? MP_OBJ_NEW_SMALL_INT(-1) : args[1], false); + mp_obj_t ret = self->items[index]; + self->len -= 1; + memmove(self->items + index, self->items + index + 1, (self->len - index) * sizeof(mp_obj_t)); + // Clear stale pointer from slot which just got freed to prevent GC issues + self->items[self->len] = MP_OBJ_NULL; + if (self->alloc > LIST_MIN_ALLOC && self->alloc > 2 * self->len) { + self->items = m_renew(mp_obj_t, self->items, self->alloc, self->alloc / 2); + self->alloc /= 2; + } + return ret; +} + +STATIC void mp_quicksort(mp_obj_t *head, mp_obj_t *tail, mp_obj_t key_fn, mp_obj_t binop_less_result) { + if (MP_STACK_CHECK()) { + // TODO propagate exception + return; + } + while (head < tail) { + mp_obj_t *h = head - 1; + mp_obj_t *t = tail; + mp_obj_t v = key_fn == MP_OBJ_NULL ? tail[0] : mp_call_function_1(key_fn, tail[0]); // get pivot using key_fn + for (;;) { + do {++h; + } while (h < t && mp_binary_op(MP_BINARY_OP_LESS, key_fn == MP_OBJ_NULL ? h[0] : mp_call_function_1(key_fn, h[0]), v) == binop_less_result); + do {--t; + } while (h < t && mp_binary_op(MP_BINARY_OP_LESS, v, key_fn == MP_OBJ_NULL ? t[0] : mp_call_function_1(key_fn, t[0])) == binop_less_result); + if (h >= t) { + break; + } + mp_obj_t x = h[0]; + h[0] = t[0]; + t[0] = x; + } + mp_obj_t x = h[0]; + h[0] = tail[0]; + tail[0] = x; + // do the smaller recursive call first, to keep stack within O(log(N)) + if (t - head < tail - h - 1) { + mp_quicksort(head, t, key_fn, binop_less_result); + head = h + 1; + } else { + mp_quicksort(h + 1, tail, key_fn, binop_less_result); + tail = t; + } + } +} + +// TODO Python defines sort to be stable but ours is not +mp_obj_t mp_obj_list_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_reverse, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + struct { + mp_arg_val_t key, reverse; + } args; + if (mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args)) { + return MP_OBJ_NULL; + } + + mp_check_self(mp_obj_is_type(pos_args[0], &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + if (self->len > 1) { + mp_quicksort(self->items, self->items + self->len - 1, + args.key.u_obj == mp_const_none ? MP_OBJ_NULL : args.key.u_obj, + args.reverse.u_bool ? mp_const_false : mp_const_true); + } + + return mp_const_none; +} + +STATIC mp_obj_t list_clear(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + self->len = 0; + self->items = m_renew(mp_obj_t, self->items, self->alloc, LIST_MIN_ALLOC); + self->alloc = LIST_MIN_ALLOC; + mp_seq_clear(self->items, 0, self->alloc, sizeof(*self->items)); + return mp_const_none; +} + +STATIC mp_obj_t list_copy(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_list(self->len, self->items); +} + +STATIC mp_obj_t list_count(mp_obj_t self_in, mp_obj_t value) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + return mp_seq_count_obj(self->items, self->len, value); +} + +STATIC mp_obj_t list_index(size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_type(args[0], &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(args[0]); + return mp_seq_index_obj(self->items, self->len, n_args, args); +} + +STATIC mp_obj_t list_insert(mp_obj_t self_in, mp_obj_t idx, mp_obj_t obj) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + // insert has its own strange index logic + mp_int_t index = MP_OBJ_SMALL_INT_VALUE(idx); + if (index < 0) { + index += self->len; + } + if (index < 0) { + index = 0; + } + if ((size_t)index > self->len) { + index = self->len; + } + + mp_obj_list_append(self_in, mp_const_none); + + for (mp_int_t i = self->len - 1; i > index; i--) { + self->items[i] = self->items[i - 1]; + } + self->items[index] = obj; + + return mp_const_none; +} + +mp_obj_t mp_obj_list_remove(mp_obj_t self_in, mp_obj_t value) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_t args[] = {self_in, value}; + args[1] = list_index(2, args); + if (args[1] == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } + list_pop(2, args); + + return mp_const_none; +} + +STATIC mp_obj_t list_reverse(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &mp_type_list)); + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + + mp_int_t len = self->len; + for (mp_int_t i = 0; i < len / 2; i++) { + mp_obj_t a = self->items[i]; + self->items[i] = self->items[len - i - 1]; + self->items[len - i - 1] = a; + } + + return mp_const_none; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_append_obj, mp_obj_list_append); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_extend_obj, list_extend); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_clear_obj, list_clear); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_copy_obj, list_copy); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_count_obj, list_count); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(list_index_obj, 2, 4, list_index); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(list_insert_obj, list_insert); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(list_pop_obj, 1, 2, list_pop); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(list_remove_obj, mp_obj_list_remove); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(list_reverse_obj, list_reverse); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(list_sort_obj, 1, mp_obj_list_sort); + +STATIC const mp_rom_map_elem_t list_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&list_append_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&list_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&list_copy_obj) }, + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&list_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&list_extend_obj) }, + { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&list_index_obj) }, + { MP_ROM_QSTR(MP_QSTR_insert), MP_ROM_PTR(&list_insert_obj) }, + { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&list_pop_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&list_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_reverse), MP_ROM_PTR(&list_reverse_obj) }, + { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&list_sort_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(list_locals_dict, list_locals_dict_table); + +const mp_obj_type_t mp_type_list = { + { &mp_type_type }, + .name = MP_QSTR_list, + .print = list_print, + .make_new = list_make_new, + .unary_op = list_unary_op, + .binary_op = list_binary_op, + .subscr = list_subscr, + .getiter = list_getiter, + .locals_dict = (mp_obj_dict_t *)&list_locals_dict, +}; + +mp_obj_list_t *mp_obj_list_init(mp_obj_list_t *o, size_t n) { + o->base.type = &mp_type_list; + o->alloc = n < LIST_MIN_ALLOC ? LIST_MIN_ALLOC : n; + o->len = n; + o->items = m_new(mp_obj_t, o->alloc); + if (o->items == NULL) { + return NULL; + } + mp_seq_clear(o->items, n, o->alloc, sizeof(*o->items)); + return o; +} + +STATIC mp_obj_list_t *list_new(size_t n) { + mp_obj_list_t *o = m_new_obj(mp_obj_list_t); + if (o == NULL) { + return NULL; + } + return mp_obj_list_init(o, n); +} + +mp_obj_t mp_obj_new_list(size_t n, mp_obj_t *items) { + mp_obj_list_t *o = list_new(n); + if (o != NULL && items != NULL) { + for (size_t i = 0; i < n; i++) { + o->items[i] = items[i]; + } + } + return MP_OBJ_FROM_PTR(o); +} + +void mp_obj_list_get(mp_obj_t self_in, size_t *len, mp_obj_t **items) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + *len = self->len; + *items = self->items; +} + +void mp_obj_list_set_len(mp_obj_t self_in, size_t len) { + // trust that the caller knows what it's doing + // TODO realloc if len got much smaller than alloc + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + self->len = len; +} + +void mp_obj_list_store(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + mp_obj_list_t *self = MP_OBJ_TO_PTR(self_in); + size_t i = mp_get_index(self->base.type, self->len, index, false); + self->items[i] = value; +} + +/******************************************************************************/ +/* list iterator */ + +typedef struct _mp_obj_list_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t list; + size_t cur; +} mp_obj_list_it_t; + +STATIC mp_obj_t list_it_iternext(mp_obj_t self_in) { + mp_obj_list_it_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_list_t *list = MP_OBJ_TO_PTR(self->list); + if (self->cur < list->len) { + mp_obj_t o_out = list->items[self->cur]; + self->cur += 1; + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +mp_obj_t mp_obj_new_list_iterator(mp_obj_t list, size_t cur, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_list_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_list_it_t *o = (mp_obj_list_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = list_it_iternext; + o->list = list; + o->cur = cur; + return MP_OBJ_FROM_PTR(o); +} diff --git a/ports/wapy/objstr_no_nlr.c b/ports/wapy/objstr_no_nlr.c new file mode 100644 index 000000000..ba60b649b --- /dev/null +++ b/ports/wapy/objstr_no_nlr.c @@ -0,0 +1,2320 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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/unicode.h" +#include "py/objstr.h" +#include "py/objlist.h" +#include "py/runtime.h" +#include "py/stackctrl.h" + +#if MICROPY_PY_BUILTINS_STR_OP_MODULO +STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict); +#endif + +STATIC mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); +STATIC mp_obj_t bad_implicit_conversion(mp_obj_t self_in); + +/******************************************************************************/ +/* str */ + +void mp_str_print_quoted(const mp_print_t *print, const byte *str_data, size_t str_len, bool is_bytes) { + // this escapes characters, but it will be very slow to print (calling print many times) + bool has_single_quote = false; + bool has_double_quote = false; + for (const byte *s = str_data, *top = str_data + str_len; !has_double_quote && s < top; s++) { + if (*s == '\'') { + has_single_quote = true; + } else if (*s == '"') { + has_double_quote = true; + } + } + int quote_char = '\''; + if (has_single_quote && !has_double_quote) { + quote_char = '"'; + } + mp_printf(print, "%c", quote_char); + for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) { + if (*s == quote_char) { + mp_printf(print, "\\%c", quote_char); + } else if (*s == '\\') { + mp_print_str(print, "\\\\"); + } else if (*s >= 0x20 && *s != 0x7f && (!is_bytes || *s < 0x80)) { + // In strings, anything which is not ascii control character + // is printed as is, this includes characters in range 0x80-0xff + // (which can be non-Latin letters, etc.) + mp_printf(print, "%c", *s); + } else if (*s == '\n') { + mp_print_str(print, "\\n"); + } else if (*s == '\r') { + mp_print_str(print, "\\r"); + } else if (*s == '\t') { + mp_print_str(print, "\\t"); + } else { + mp_printf(print, "\\x%02x", *s); + } + } + mp_printf(print, "%c", quote_char); +} + +#if MICROPY_PY_UJSON +void mp_str_print_json(const mp_print_t *print, const byte *str_data, size_t str_len) { + // for JSON spec, see http://www.ietf.org/rfc/rfc4627.txt + // if we are given a valid utf8-encoded string, we will print it in a JSON-conforming way + mp_print_str(print, "\""); + for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) { + if (*s == '"' || *s == '\\') { + mp_printf(print, "\\%c", *s); + } else if (*s >= 32) { + // this will handle normal and utf-8 encoded chars + mp_printf(print, "%c", *s); + } else if (*s == '\n') { + mp_print_str(print, "\\n"); + } else if (*s == '\r') { + mp_print_str(print, "\\r"); + } else if (*s == '\t') { + mp_print_str(print, "\\t"); + } else { + // this will handle control chars + mp_printf(print, "\\u%04x", *s); + } + } + mp_print_str(print, "\""); +} +#endif + +STATIC void str_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + GET_STR_DATA_LEN(self_in, str_data, str_len); + #if MICROPY_PY_UJSON + if (kind == PRINT_JSON) { + mp_str_print_json(print, str_data, str_len); + return; + } + #endif + #if !MICROPY_PY_BUILTINS_STR_UNICODE + bool is_bytes = mp_obj_is_type(self_in, &mp_type_bytes); + #else + 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); + } else { + if (is_bytes) { + mp_print_str(print, "b"); + } + mp_str_print_quoted(print, str_data, str_len, is_bytes); + } +} +#include +mp_obj_t mp_obj_str_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + #if MICROPY_CPYTHON_COMPAT + if (n_kw != 0) { + mp_arg_error_unimpl_kw(); + } + #endif + + if (mp_arg_check_num(n_args, n_kw, 0, 3, false)) { + return MP_OBJ_NULL; + } + + switch (n_args) { + case 0: + return MP_OBJ_NEW_QSTR(MP_QSTR_); + + case 1: { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + mp_obj_print_helper(&print, args[0], PRINT_STR); + return mp_obj_new_str_from_vstr(type, &vstr); + } + + default: // 2 or 3 args +#pragma message "TODO: validate 2nd/3rd args" + if (mp_obj_is_type(args[0], &mp_type_bytes)) { + GET_STR_DATA_LEN(args[0], str_data, str_len); + GET_STR_HASH(args[0], str_hash); + if (str_hash == 0) { + str_hash = qstr_compute_hash(str_data, str_len); + } + #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK + if (!utf8_check(str_data, str_len)) { + mp_raise_msg(&mp_type_UnicodeError, NULL); + } + #endif + + // Check if a qstr with this data already exists + qstr q = qstr_find_strn((const char *)str_data, str_len); + if (q != MP_QSTRnull) { + return MP_OBJ_NEW_QSTR(q); + } + + mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(type, NULL, str_len)); + o->data = str_data; + o->hash = str_hash; + return MP_OBJ_FROM_PTR(o); + } else { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); + #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK + if (!utf8_check(bufinfo.buf, bufinfo.len)) { + mp_raise_msg(&mp_type_UnicodeError, NULL); + } + #endif + return mp_obj_new_str(bufinfo.buf, bufinfo.len); + } + } +} + +STATIC mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type_in; + + #if MICROPY_CPYTHON_COMPAT + if (n_kw != 0) { + mp_arg_error_unimpl_kw(); + } + #else + (void)n_kw; + #endif + + if (n_args == 0) { + return mp_const_empty_bytes; + } + + if (mp_obj_is_str(args[0])) { + if (n_args < 2 || n_args > 3) { + goto wrong_args; + } + GET_STR_DATA_LEN(args[0], str_data, str_len); + GET_STR_HASH(args[0], str_hash); + if (str_hash == 0) { + str_hash = qstr_compute_hash(str_data, str_len); + } + mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(&mp_type_bytes, NULL, str_len)); + o->data = str_data; + o->hash = str_hash; + return MP_OBJ_FROM_PTR(o); + } + + if (n_args > 1) { + goto wrong_args; + } + + if (mp_obj_is_small_int(args[0])) { + mp_int_t len = MP_OBJ_SMALL_INT_VALUE(args[0]); + if (len < 0) { + mp_raise_ValueError(NULL); + } + vstr_t vstr; + vstr_init_len(&vstr, len); + memset(vstr.buf, 0, len); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); + } + + // check if argument has the buffer protocol + mp_buffer_info_t bufinfo; + if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_READ)) { + return mp_obj_new_bytes(bufinfo.buf, bufinfo.len); + } + + vstr_t vstr; + // Try to create array of exact len if initializer len is known + mp_obj_t len_in = mp_obj_len_maybe(args[0]); + if (len_in == MP_OBJ_NULL) { + vstr_init(&vstr, 16); + } else { + mp_int_t len = MP_OBJ_SMALL_INT_VALUE(len_in); + vstr_init(&vstr, len); + } + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + mp_obj_t item; + while ((item = mp_iternext2(iterable)) != MP_OBJ_NULL) { + mp_int_t val = mp_obj_get_int(item); + #if MICROPY_FULL_CHECKS + if (val < 0 || val > 255) { + mp_raise_ValueError("bytes value out of range"); + } + #endif + vstr_add_byte(&vstr, val); + } + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } + + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); + +wrong_args: + mp_raise_TypeError(MP_ERROR_TEXT("wrong number of arguments")); +} + +// like strstr but with specified length and allows \0 bytes +// TODO replace with something more efficient/standard +const byte *find_subbytes(const byte *haystack, size_t hlen, const byte *needle, size_t nlen, int direction) { + if (hlen >= nlen) { + size_t str_index, str_index_end; + if (direction > 0) { + str_index = 0; + str_index_end = hlen - nlen; + } else { + str_index = hlen - nlen; + str_index_end = 0; + } + for (;;) { + if (memcmp(&haystack[str_index], needle, nlen) == 0) { + //found + return haystack + str_index; + } + if (str_index == str_index_end) { + //not found + break; + } + str_index += direction; + } + } + return NULL; +} + +// Note: this function is used to check if an object is a str or bytes, which +// works because both those types use it as their binary_op method. Revisit +// mp_obj_is_str_or_bytes if this fact changes. +mp_obj_t mp_obj_str_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { + // check for modulo + if (op == MP_BINARY_OP_MODULO) { + #if MICROPY_PY_BUILTINS_STR_OP_MODULO + mp_obj_t *args = &rhs_in; + size_t n_args = 1; + mp_obj_t dict = MP_OBJ_NULL; + if (mp_obj_is_type(rhs_in, &mp_type_tuple)) { + // TODO: Support tuple subclasses? + mp_obj_tuple_get(rhs_in, &n_args, &args); + } else if (mp_obj_is_type(rhs_in, &mp_type_dict)) { + dict = rhs_in; + } + return str_modulo_format(lhs_in, n_args, args, dict); + #else + return MP_OBJ_NULL; + #endif + } + + // from now on we need lhs type and data, so extract them + const mp_obj_type_t *lhs_type = mp_obj_get_type(lhs_in); + GET_STR_DATA_LEN(lhs_in, lhs_data, lhs_len); + + // check for multiply + if (op == MP_BINARY_OP_MULTIPLY) { + mp_int_t n; + if (!mp_obj_get_int_maybe(rhs_in, &n)) { + return MP_OBJ_NULL; // op not supported + } + if (n <= 0) { + if (lhs_type == &mp_type_str) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); // empty str + } else { + return mp_const_empty_bytes; + } + } + vstr_t vstr; + vstr_init_len(&vstr, lhs_len * n); + mp_seq_multiply(lhs_data, sizeof(*lhs_data), lhs_len, n, vstr.buf); + return mp_obj_new_str_from_vstr(lhs_type, &vstr); + } + + // From now on all operations allow: + // - str with str + // - bytes with bytes + // - bytes with bytearray + // - bytes with array.array + // To do this efficiently we use the buffer protocol to extract the raw + // data for the rhs, but only if the lhs is a bytes object. + // + // NOTE: CPython does not allow comparison between bytes ard array.array + // (even if the array is of type 'b'), even though it allows addition of + // such types. We are not compatible with this (we do allow comparison + // of bytes with anything that has the buffer protocol). It would be + // easy to "fix" this with a bit of extra logic below, but it costs code + // size and execution time so we don't. + + const byte *rhs_data; + size_t rhs_len; + if (lhs_type == mp_obj_get_type(rhs_in)) { + GET_STR_DATA_LEN(rhs_in, rhs_data_, rhs_len_); + rhs_data = rhs_data_; + rhs_len = rhs_len_; + } else if (lhs_type == &mp_type_bytes) { + mp_buffer_info_t bufinfo; + if (!mp_get_buffer(rhs_in, &bufinfo, MP_BUFFER_READ)) { + return MP_OBJ_NULL; // op not supported + } + rhs_data = bufinfo.buf; + rhs_len = bufinfo.len; + } else { + // LHS is str and RHS has an incompatible type + // (except if operation is EQUAL, but that's handled by mp_obj_equal) + return bad_implicit_conversion(rhs_in); + } + + switch (op) { + case MP_BINARY_OP_ADD: + case MP_BINARY_OP_INPLACE_ADD: { + if (lhs_len == 0 && mp_obj_get_type(rhs_in) == lhs_type) { + return rhs_in; + } + if (rhs_len == 0) { + return lhs_in; + } + + vstr_t vstr; + vstr_init_len(&vstr, lhs_len + rhs_len); + memcpy(vstr.buf, lhs_data, lhs_len); + memcpy(vstr.buf + lhs_len, rhs_data, rhs_len); + return mp_obj_new_str_from_vstr(lhs_type, &vstr); + } + + case MP_BINARY_OP_CONTAINS: + return mp_obj_new_bool(find_subbytes(lhs_data, lhs_len, rhs_data, rhs_len, 1) != NULL); + + //case MP_BINARY_OP_NOT_EQUAL: // This is never passed here + case MP_BINARY_OP_EQUAL: // This will be passed only for bytes, str is dealt with in mp_obj_equal() + case MP_BINARY_OP_LESS: + case MP_BINARY_OP_LESS_EQUAL: + case MP_BINARY_OP_MORE: + case MP_BINARY_OP_MORE_EQUAL: + return mp_obj_new_bool(mp_seq_cmp_bytes(op, lhs_data, lhs_len, rhs_data, rhs_len)); + + default: + return MP_OBJ_NULL; // op not supported + } +} + +#if !MICROPY_PY_BUILTINS_STR_UNICODE +// objstrunicode defines own version +const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, size_t self_len, + mp_obj_t index, bool is_slice) { + size_t index_val = mp_get_index(type, self_len, index, is_slice); + return self_data + index_val; +} +#endif + +// This is used for both bytes and 8-bit strings. This is not used for unicode strings. +STATIC mp_obj_t bytes_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + const mp_obj_type_t *type = mp_obj_get_type(self_in); + GET_STR_DATA_LEN(self_in, self_data, self_len); + if (value == MP_OBJ_SENTINEL) { + // load + #if MICROPY_PY_BUILTINS_SLICE + if (mp_obj_is_type(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(self_len, index, &slice)) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + return mp_obj_new_str_of_type(type, self_data + slice.start, slice.stop - slice.start); + } + #endif + size_t index_val = mp_get_index(type, self_len, index, false); + if (index_val == (size_t)-1) { + return MP_OBJ_NULL; // exception + } + // If we have unicode enabled the type will always be bytes, so take the short cut. + if (MICROPY_PY_BUILTINS_STR_UNICODE || type == &mp_type_bytes) { + return MP_OBJ_NEW_SMALL_INT(self_data[index_val]); + } else { + return mp_obj_new_str_via_qstr((char *)&self_data[index_val], 1); + } + } else { + return MP_OBJ_NULL; // op not supported + } +} + +STATIC mp_obj_t str_join(mp_obj_t self_in, mp_obj_t arg) { + mp_check_self(mp_obj_is_str_or_bytes(self_in)); + const mp_obj_type_t *self_type = mp_obj_get_type(self_in); + + // get separation string + GET_STR_DATA_LEN(self_in, sep_str, sep_len); + + // process args + size_t seq_len; + mp_obj_t *seq_items; + + if (!mp_obj_is_type(arg, &mp_type_list) && !mp_obj_is_type(arg, &mp_type_tuple)) { + // arg is not a list nor a tuple, try to convert it to a list + // TODO: Try to optimize? + arg = mp_type_list.make_new(&mp_type_list, 1, 0, &arg); + if (arg == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + } + mp_obj_get_array(arg, &seq_len, &seq_items); + + // count required length + size_t required_len = 0; + for (size_t i = 0; i < seq_len; i++) { + if (mp_obj_get_type(seq_items[i]) != self_type) { + mp_raise_TypeError( + MP_ERROR_TEXT("join expects a list of str/bytes objects consistent with self object")); + } + if (i > 0) { + required_len += sep_len; + } + GET_STR_LEN(seq_items[i], l); + required_len += l; + } + + // make joined string + vstr_t vstr; + vstr_init_len(&vstr, required_len); + byte *data = (byte *)vstr.buf; + for (size_t i = 0; i < seq_len; i++) { + if (i > 0) { + memcpy(data, sep_str, sep_len); + data += sep_len; + } + GET_STR_DATA_LEN(seq_items[i], s, l); + memcpy(data, s, l); + data += l; + } + + // return joined string + return mp_obj_new_str_from_vstr(self_type, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_join_obj, str_join); + +mp_obj_t mp_obj_str_split(size_t n_args, const mp_obj_t *args) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_int_t splits = -1; + mp_obj_t sep = mp_const_none; + if (n_args > 1) { + sep = args[1]; + if (n_args > 2) { + splits = mp_obj_get_int(args[2]); + } + } + + mp_obj_t res = mp_obj_new_list(0, NULL); + GET_STR_DATA_LEN(args[0], s, len); + const byte *top = s + len; + + if (sep == mp_const_none) { + // sep not given, so separate on whitespace + + // Initial whitespace is not counted as split, so we pre-do it + while (s < top && unichar_isspace(*s)) { + s++; + } + while (s < top && splits != 0) { + const byte *start = s; + while (s < top && !unichar_isspace(*s)) { + s++; + } + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, s - start)); + if (s >= top) { + break; + } + while (s < top && unichar_isspace(*s)) { + s++; + } + if (splits > 0) { + splits--; + } + } + + if (s < top) { + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, s, top - s)); + } + + } else { + // sep given + if (mp_obj_get_type(sep) != self_type) { + return bad_implicit_conversion(sep); + } + + size_t sep_len; + const char *sep_str = mp_obj_str_get_data(sep, &sep_len); + + if (sep_len == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); + } + + for (;;) { + const byte *start = s; + for (;;) { + if (splits == 0 || s + sep_len > top) { + s = top; + break; + } else if (memcmp(s, sep_str, sep_len) == 0) { + break; + } + s++; + } + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, s - start)); + if (s >= top) { + break; + } + s += sep_len; + if (splits > 0) { + splits--; + } + } + } + + return res; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_split_obj, 1, 3, mp_obj_str_split); + +#if MICROPY_PY_BUILTINS_STR_SPLITLINES +STATIC mp_obj_t str_splitlines(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_keepends }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_keepends, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + 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); + + const mp_obj_type_t *self_type = mp_obj_get_type(pos_args[0]); + mp_obj_t res = mp_obj_new_list(0, NULL); + + GET_STR_DATA_LEN(pos_args[0], s, len); + const byte *top = s + len; + + while (s < top) { + const byte *start = s; + size_t match = 0; + while (s < top) { + if (*s == '\n') { + match = 1; + break; + } else if (*s == '\r') { + if (s[1] == '\n') { + match = 2; + } else { + match = 1; + } + break; + } + s++; + } + size_t sub_len = s - start; + if (args[ARG_keepends].u_bool) { + sub_len += match; + } + mp_obj_list_append(res, mp_obj_new_str_of_type(self_type, start, sub_len)); + s += match; + } + + return res; +} +MP_DEFINE_CONST_FUN_OBJ_KW(str_splitlines_obj, 1, str_splitlines); +#endif + +STATIC mp_obj_t str_rsplit(size_t n_args, const mp_obj_t *args) { + if (n_args < 3) { + // If we don't have split limit, it doesn't matter from which side + // we split. + return mp_obj_str_split(n_args, args); + } + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_obj_t sep = args[1]; + GET_STR_DATA_LEN(args[0], s, len); + + mp_int_t splits = mp_obj_get_int(args[2]); + if (splits < 0) { + // Negative limit means no limit, so delegate to split(). + return mp_obj_str_split(n_args, args); + } + + mp_int_t org_splits = splits; + // Preallocate list to the max expected # of elements, as we + // will fill it from the end. + mp_obj_list_t *res = MP_OBJ_TO_PTR(mp_obj_new_list(splits + 1, NULL)); + mp_int_t idx = splits; + + if (sep == mp_const_none) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("rsplit(None,n)")); + } else { + size_t sep_len; + const char *sep_str = mp_obj_str_get_data(sep, &sep_len); + + if (sep_len == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); + } + + const byte *beg = s; + const byte *last = s + len; + for (;;) { + s = last - sep_len; + for (;;) { + if (splits == 0 || s < beg) { + break; + } else if (memcmp(s, sep_str, sep_len) == 0) { + break; + } + s--; + } + if (s < beg || splits == 0) { + res->items[idx] = mp_obj_new_str_of_type(self_type, beg, last - beg); + break; + } + res->items[idx--] = mp_obj_new_str_of_type(self_type, s + sep_len, last - s - sep_len); + last = s; + splits--; + } + if (idx != 0) { + // We split less parts than split limit, now go cleanup surplus + size_t used = org_splits + 1 - idx; + memmove(res->items, &res->items[idx], used * sizeof(mp_obj_t)); + mp_seq_clear(res->items, used, res->alloc, sizeof(*res->items)); + res->len = used; + } + } + + return MP_OBJ_FROM_PTR(res); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rsplit_obj, 1, 3, str_rsplit); + +STATIC mp_obj_t str_finder(size_t n_args, const mp_obj_t *args, int direction, bool is_index) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + // check argument type + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + + GET_STR_DATA_LEN(args[0], haystack, haystack_len); + GET_STR_DATA_LEN(args[1], needle, needle_len); + + const byte *start = haystack; + const byte *end = haystack + haystack_len; + if (n_args >= 3 && args[2] != mp_const_none) { + start = str_index_to_ptr(self_type, haystack, haystack_len, args[2], true); + } + if (n_args >= 4 && args[3] != mp_const_none) { + end = str_index_to_ptr(self_type, haystack, haystack_len, args[3], true); + } + + if (end < start) { + goto out_error; + } + + const byte *p = find_subbytes(start, end - start, needle, needle_len, direction); + if (p == NULL) { + out_error: + // not found + if (is_index) { + mp_raise_ValueError(MP_ERROR_TEXT("substring not found")); + } else { + return MP_OBJ_NEW_SMALL_INT(-1); + } + } else { + // found + #if MICROPY_PY_BUILTINS_STR_UNICODE + if (self_type == &mp_type_str) { + return MP_OBJ_NEW_SMALL_INT(utf8_ptr_to_index(haystack, p)); + } + #endif + return MP_OBJ_NEW_SMALL_INT(p - haystack); + } +} + +STATIC mp_obj_t str_find(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, 1, false); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_find_obj, 2, 4, str_find); + +STATIC mp_obj_t str_rfind(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, -1, false); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rfind_obj, 2, 4, str_rfind); + +STATIC mp_obj_t str_index(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, 1, true); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_index_obj, 2, 4, str_index); + +STATIC mp_obj_t str_rindex(size_t n_args, const mp_obj_t *args) { + return str_finder(n_args, args, -1, true); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rindex_obj, 2, 4, str_rindex); + +// TODO: (Much) more variety in args +STATIC mp_obj_t str_startswith(size_t n_args, const mp_obj_t *args) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + GET_STR_DATA_LEN(args[0], str, str_len); + size_t prefix_len; + const char *prefix = mp_obj_str_get_data(args[1], &prefix_len); + if (prefix == NULL) { + // exception + return MP_OBJ_NULL; + } + const byte *start = str; + if (n_args > 2) { + start = str_index_to_ptr(self_type, str, str_len, args[2], true); + } + if (prefix_len + (start - str) > str_len) { + return mp_const_false; + } + return mp_obj_new_bool(memcmp(start, prefix, prefix_len) == 0); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_startswith_obj, 2, 3, str_startswith); + +STATIC mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) { + GET_STR_DATA_LEN(args[0], str, str_len); + size_t suffix_len; + const char *suffix = mp_obj_str_get_data(args[1], &suffix_len); + if (suffix == NULL) { + return MP_OBJ_NULL; + } + if (n_args > 2) { + return mp_raise_NotImplementedError_o("start/end indices"); + } + + if (suffix_len > str_len) { + return mp_const_false; + } + return mp_obj_new_bool(memcmp(str + (str_len - suffix_len), suffix, suffix_len) == 0); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_endswith_obj, 2, 3, str_endswith); + +enum { LSTRIP, RSTRIP, STRIP }; + +STATIC mp_obj_t str_uni_strip(int type, size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + + const byte *chars_to_del; + uint chars_to_del_len; + static const byte whitespace[] = " \t\n\r\v\f"; + + if (n_args == 1) { + chars_to_del = whitespace; + chars_to_del_len = sizeof(whitespace) - 1; + } else { + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + GET_STR_DATA_LEN(args[1], s, l); + chars_to_del = s; + chars_to_del_len = l; + } + + GET_STR_DATA_LEN(args[0], orig_str, orig_str_len); + + size_t first_good_char_pos = 0; + bool first_good_char_pos_set = false; + size_t last_good_char_pos = 0; + size_t i = 0; + int delta = 1; + if (type == RSTRIP) { + i = orig_str_len - 1; + delta = -1; + } + for (size_t len = orig_str_len; len > 0; len--) { + if (find_subbytes(chars_to_del, chars_to_del_len, &orig_str[i], 1, 1) == NULL) { + if (!first_good_char_pos_set) { + first_good_char_pos_set = true; + first_good_char_pos = i; + if (type == LSTRIP) { + last_good_char_pos = orig_str_len - 1; + break; + } else if (type == RSTRIP) { + first_good_char_pos = 0; + last_good_char_pos = i; + break; + } + } + last_good_char_pos = i; + } + i += delta; + } + + if (!first_good_char_pos_set) { + // string is all whitespace, return '' + if (self_type == &mp_type_str) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } else { + return mp_const_empty_bytes; + } + } + + assert(last_good_char_pos >= first_good_char_pos); + // +1 to accommodate the last character + size_t stripped_len = last_good_char_pos - first_good_char_pos + 1; + if (stripped_len == orig_str_len) { + // If nothing was stripped, don't bother to dup original string + // TODO: watch out for this case when we'll get to bytearray.strip() + assert(first_good_char_pos == 0); + return args[0]; + } + return mp_obj_new_str_of_type(self_type, orig_str + first_good_char_pos, stripped_len); +} + +STATIC mp_obj_t str_strip(size_t n_args, const mp_obj_t *args) { + return str_uni_strip(STRIP, n_args, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_strip_obj, 1, 2, str_strip); + +STATIC mp_obj_t str_lstrip(size_t n_args, const mp_obj_t *args) { + return str_uni_strip(LSTRIP, n_args, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_lstrip_obj, 1, 2, str_lstrip); + +STATIC mp_obj_t str_rstrip(size_t n_args, const mp_obj_t *args) { + return str_uni_strip(RSTRIP, n_args, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rstrip_obj, 1, 2, str_rstrip); + +#if MICROPY_PY_BUILTINS_STR_CENTER +STATIC mp_obj_t str_center(mp_obj_t str_in, mp_obj_t width_in) { + GET_STR_DATA_LEN(str_in, str, str_len); + mp_uint_t width = mp_obj_get_int(width_in); + if (str_len >= width) { + return str_in; + } + + vstr_t vstr; + vstr_init_len(&vstr, width); + memset(vstr.buf, ' ', width); + int left = (width - str_len) / 2; + memcpy(vstr.buf + left, str, str_len); + return mp_obj_new_str_from_vstr(mp_obj_get_type(str_in), &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_center_obj, str_center); +#endif + +// Takes an int arg, but only parses unsigned numbers, and only changes +// *num if at least one digit was parsed. +STATIC const char *str_to_int(const char *str, const char *top, int *num) { + if (str < top && '0' <= *str && *str <= '9') { + *num = 0; + do { + *num = *num * 10 + (*str - '0'); + str++; + } + while (str < top && '0' <= *str && *str <= '9'); + } + return str; +} + +STATIC bool isalignment(char ch) { + return ch && strchr("<>=^", ch) != NULL; +} + +STATIC bool istype(char ch) { + return ch && strchr("bcdeEfFgGnosxX%", ch) != NULL; +} + +STATIC bool arg_looks_integer(mp_obj_t arg) { + return mp_obj_is_bool(arg) || mp_obj_is_int(arg); +} + +STATIC bool arg_looks_numeric(mp_obj_t arg) { + return arg_looks_integer(arg) + #if MICROPY_PY_BUILTINS_FLOAT + || mp_obj_is_float(arg) + #endif + ; +} + +#if MICROPY_PY_BUILTINS_STR_OP_MODULO +STATIC mp_obj_t arg_as_int(mp_obj_t arg) { + #if MICROPY_PY_BUILTINS_FLOAT + if (mp_obj_is_float(arg)) { + return mp_obj_new_int_from_float(mp_obj_float_get(arg)); + } + #endif + return arg; +} +#endif + +#if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE +STATIC mp_obj_t terse_str_format_value_error(void) { + mp_raise_ValueError(MP_ERROR_TEXT("bad format string")); +} +#else +// define to nothing to improve coverage +/* +static inline mp_obj_t terse_str_format_value_error(void) { + return MP_OBJ_NULL; +}*/ +#endif + +STATIC vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *arg_i, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + + for (; str < top; str++) { + if (*str == '}') { + str++; + if (str < top && *str == '}') { + vstr_add_byte(&vstr, '}'); + continue; + } + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("single '}' encountered in format string"))); + vstr.buf = NULL; + return vstr; + #endif + } + if (*str != '{') { + vstr_add_byte(&vstr, *str); + continue; + } + + str++; + if (str < top && *str == '{') { + vstr_add_byte(&vstr, '{'); + continue; + } + + // replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}" + + const char *field_name = NULL; + const char *field_name_top = NULL; + char conversion = '\0'; + const char *format_spec = NULL; + + if (str < top && *str != '}' && *str != '!' && *str != ':') { + field_name = (const char *)str; + while (str < top && *str != '}' && *str != '!' && *str != ':') { + ++str; + } + field_name_top = (const char *)str; + } + + // conversion ::= "r" | "s" + + if (str < top && *str == '!') { + str++; + if (str < top && (*str == 'r' || *str == 's')) { + conversion = *str++; + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL + mp_raise_ValueError_o(MP_ERROR_TEXT("bad conversion specifier")); + #else + + if (str >= top) { + mp_raise_ValueError_o( + MP_ERROR_TEXT("end of format while looking for conversion specifier")); + } else { + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown conversion specifier %c"), *str)); + } + #endif + vstr.buf = NULL; + return vstr; + } + } + + if (str < top && *str == ':') { + str++; + // {:} is the same as {}, which is the same as {!s} + // This makes a difference when passing in a True or False + // '{}'.format(True) returns 'True' + // '{:d}'.format(True) returns '1' + // So we treat {:} as {} and this later gets treated to be {!s} + if (*str != '}') { + format_spec = str; + for (int nest = 1; str < top;) { + if (*str == '{') { + ++nest; + } else if (*str == '}') { + if (--nest == 0) { + break; + } + } + ++str; + } + } + } + if (str >= top) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("unmatched '{' in format")); + #endif + vstr.buf = NULL; + return vstr; + } + if (*str != '}') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("expected ':' after format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + + mp_obj_t arg = mp_const_none; + + if (field_name) { + int index = 0; + if (MP_LIKELY(unichar_isdigit(*field_name))) { + if (*arg_i > 0) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("can't switch from automatic field numbering to manual field specification")); + #endif + vstr.buf = NULL; + return vstr; + } + field_name = str_to_int(field_name, field_name_top, &index); + if ((uint)index >= n_args - 1) { + mp_raise_msg_o(&mp_type_IndexError, MP_ERROR_TEXT("tuple index out of range") ); + vstr.buf = NULL; + return vstr; + } + arg = args[index + 1]; + *arg_i = -1; + } else { + const char *lookup; + for (lookup = field_name; lookup < field_name_top && *lookup != '.' && *lookup != '['; lookup++) { + ;//pass + } + mp_obj_t field_q = mp_obj_new_str_via_qstr(field_name, lookup - field_name); // should it be via qstr? + field_name = lookup; + mp_map_elem_t *key_elem = mp_map_lookup(kwargs, field_q, MP_MAP_LOOKUP); + if (key_elem == NULL) { + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_KeyError, field_q)); + vstr.buf = NULL; + return vstr; + } + arg = key_elem->value; + } + if (field_name < field_name_top) { + mp_raise_NotImplementedError_o( MP_ERROR_TEXT("attributes not supported yet") ); + vstr.buf = NULL; + return vstr; + } + } else { + if (*arg_i < 0) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("can't switch from manual field specification to automatic field numbering") ); + #endif + vstr.buf = NULL; + return vstr; + } + if ((uint)*arg_i >= n_args - 1) { + mp_raise_msg_o(&mp_type_IndexError, MP_ERROR_TEXT("tuple index out of range") ); + vstr.buf = NULL; + return vstr; + } + arg = args[(*arg_i) + 1]; + (*arg_i)++; + } + if (!format_spec && !conversion) { + conversion = 's'; + } + if (conversion) { + mp_print_kind_t print_kind; + if (conversion == 's') { + print_kind = PRINT_STR; + } else { + assert(conversion == 'r'); + print_kind = PRINT_REPR; + } + vstr_t arg_vstr; + mp_print_t arg_print; + vstr_init_print(&arg_vstr, 16, &arg_print); + mp_obj_print_helper(&arg_print, arg, print_kind); + arg = mp_obj_new_str_from_vstr(&mp_type_str, &arg_vstr); + } + + char fill = '\0'; + char align = '\0'; + int width = -1; + int precision = -1; + char type = '\0'; + int flags = 0; + + if (format_spec) { + // The format specifier (from http://docs.python.org/2/library/string.html#formatspec) + // + // [[fill]align][sign][#][0][width][,][.precision][type] + // fill ::= + // align ::= "<" | ">" | "=" | "^" + // sign ::= "+" | "-" | " " + // width ::= integer + // precision ::= integer + // type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%" + + // recursively call the formatter to format any nested specifiers + if (MP_STACK_CHECK()) { + vstr.buf = NULL; + return vstr; + } + vstr_t format_spec_vstr = mp_obj_str_format_helper(format_spec, str, arg_i, n_args, args, kwargs); + if (format_spec_vstr.buf == NULL) { + return format_spec_vstr; + } + const char *s = vstr_null_terminated_str(&format_spec_vstr); + const char *stop = s + format_spec_vstr.len; + if (isalignment(*s)) { + align = *s++; + } else if (*s && isalignment(s[1])) { + fill = *s++; + align = *s++; + } + if (*s == '+' || *s == '-' || *s == ' ') { + if (*s == '+') { + flags |= PF_FLAG_SHOW_SIGN; + } else if (*s == ' ') { + flags |= PF_FLAG_SPACE_SIGN; + } + s++; + } + if (*s == '#') { + flags |= PF_FLAG_SHOW_PREFIX; + s++; + } + if (*s == '0') { + if (!align) { + align = '='; + } + if (!fill) { + fill = '0'; + } + } + s = str_to_int(s, stop, &width); + if (*s == ',') { + flags |= PF_FLAG_SHOW_COMMA; + s++; + } + if (*s == '.') { + s++; + s = str_to_int(s, stop, &precision); + } + if (istype(*s)) { + type = *s++; + } + if (*s) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("invalid format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + vstr_clear(&format_spec_vstr); + } + if (!align) { + if (arg_looks_numeric(arg)) { + align = '>'; + } else { + align = '<'; + } + } + if (!fill) { + fill = ' '; + } + + if (flags & (PF_FLAG_SHOW_SIGN | PF_FLAG_SPACE_SIGN)) { + if (type == 's') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o(MP_ERROR_TEXT("sign not allowed in string format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + if (type == 'c') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("sign not allowed with integer format specifier 'c'")); + #endif + vstr.buf = NULL; + return vstr; + } + } + + switch (align) { + case '<': + flags |= PF_FLAG_LEFT_ADJUST; + break; + case '=': + flags |= PF_FLAG_PAD_AFTER_SIGN; + break; + case '^': + flags |= PF_FLAG_CENTER_ADJUST; + break; + } + + if (arg_looks_integer(arg)) { + switch (type) { + case 'b': + mp_print_mp_int(&print, arg, 2, 'a', flags, fill, width, 0); + continue; + + case 'c': { + char ch = mp_obj_get_int(arg); + mp_print_strn(&print, &ch, 1, flags, fill, width); + continue; + } + + case '\0': // No explicit format type implies 'd' + case 'n': // I don't think we support locales in uPy so use 'd' + case 'd': + mp_print_mp_int(&print, arg, 10, 'a', flags, fill, width, 0); + continue; + + case 'o': + if (flags & PF_FLAG_SHOW_PREFIX) { + flags |= PF_FLAG_SHOW_OCTAL_LETTER; + } + + mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, 0); + continue; + + case 'X': + case 'x': + mp_print_mp_int(&print, arg, 16, type - ('X' - 'A'), flags, fill, width, 0); + continue; + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case '%': + // The floating point formatters all work with anything that + // looks like an integer + break; + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), + type, mp_obj_get_type_str(arg))); + #endif + vstr.buf = NULL; + return vstr; + } + } + + // NOTE: no else here. We need the e, f, g etc formats for integer + // arguments (from above if) to take this if. + if (arg_looks_numeric(arg)) { + if (!type) { + + // Even though the docs say that an unspecified type is the same + // as 'g', there is one subtle difference, when the exponent + // is one less than the precision. + // + // '{:10.1}'.format(0.0) ==> '0e+00' + // '{:10.1g}'.format(0.0) ==> '0' + // + // TODO: Figure out how to deal with this. + // + // A proper solution would involve adding a special flag + // or something to format_float, and create a format_double + // to deal with doubles. In order to fix this when using + // sprintf, we'd need to use the e format and tweak the + // returned result to strip trailing zeros like the g format + // does. + // + // {:10.3} and {:10.2e} with 1.23e2 both produce 1.23e+02 + // but with 1.e2 you get 1e+02 and 1.00e+02 + // + // Stripping the trailing 0's (like g) does would make the + // e format give us the right format. + // + // CPython sources say: + // Omitted type specifier. Behaves in the same way as repr(x) + // and str(x) if no precision is given, else like 'g', but with + // at least one digit after the decimal point. */ + + type = 'g'; + } + if (type == 'n') { + type = 'g'; + } + + switch (type) { + #if MICROPY_PY_BUILTINS_FLOAT + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + mp_print_float(&print, mp_obj_get_float(arg), type, flags, fill, width, precision); + break; + + case '%': + flags |= PF_FLAG_ADD_PERCENT; + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + #define F100 100.0F + #else + #define F100 100.0 + #endif + mp_print_float(&print, mp_obj_get_float(arg) * F100, 'f', flags, fill, width, precision); +#undef F100 + break; + #endif + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), + type, mp_obj_get_type_str(arg))); + #endif + vstr.buf = NULL; + return vstr; + } + } else { + // arg doesn't look like a number + + if (align == '=') { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_ValueError_o( + MP_ERROR_TEXT("'=' alignment not allowed in string format specifier")); + #endif + vstr.buf = NULL; + return vstr; + } + + switch (type) { + case '\0': // no explicit format type implies 's' + case 's': { + size_t slen; + const char *s = mp_obj_str_get_data(arg, &slen); + if (precision < 0) { + precision = slen; + } + if (slen > (size_t)precision) { + slen = precision; + } + mp_print_strn(&print, s, slen, flags, fill, width); + break; + } + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + terse_str_format_value_error(); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unknown format code '%c' for object of type '%s'"), + type, mp_obj_get_type_str(arg))); + #endif + vstr.buf = NULL; + return vstr; + } + } + } + + return vstr; +} + +mp_obj_t mp_obj_str_format(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + GET_STR_DATA_LEN(args[0], str, len); + int arg_i = 0; + vstr_t vstr = mp_obj_str_format_helper((const char *)str, (const char *)str + len, &arg_i, n_args, args, kwargs); + if (vstr.buf == NULL) { + return MP_OBJ_NULL; + } + return mp_obj_new_str_from_vstr(mp_obj_get_type(args[0]), &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_KW(str_format_obj, 1, mp_obj_str_format); + +#if MICROPY_PY_BUILTINS_STR_OP_MODULO +STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict) { + mp_check_self(mp_obj_is_str_or_bytes(pattern)); + + GET_STR_DATA_LEN(pattern, str, len); + const byte *start_str = str; + bool is_bytes = mp_obj_is_type(pattern, &mp_type_bytes); + size_t arg_i = 0; + vstr_t vstr; + mp_print_t print; + vstr_init_print(&vstr, 16, &print); + + for (const byte *top = str + len; str < top; str++) { + mp_obj_t arg = MP_OBJ_NULL; + if (*str != '%') { + vstr_add_byte(&vstr, *str); + continue; + } + if (++str >= top) { + goto incomplete_format; + } + if (*str == '%') { + vstr_add_byte(&vstr, '%'); + continue; + } + + // Dictionary value lookup + if (*str == '(') { + if (dict == MP_OBJ_NULL) { + mp_raise_TypeError(MP_ERROR_TEXT("format needs a dict")); + } + arg_i = 1; // we used up the single dict argument + const byte *key = ++str; + while (*str != ')') { + if (str >= top) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return terse_str_format_value_error(); + #else + mp_raise_ValueError(MP_ERROR_TEXT("incomplete format key")); + #endif + } + ++str; + } + mp_obj_t k_obj = mp_obj_new_str_via_qstr((const char *)key, str - key); + arg = mp_obj_dict_get(dict, k_obj); + if (arg == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + str++; + } + + int flags = 0; + char fill = ' '; + int alt = 0; + while (str < top) { + if (*str == '-') { + flags |= PF_FLAG_LEFT_ADJUST; + } else if (*str == '+') { + flags |= PF_FLAG_SHOW_SIGN; + } else if (*str == ' ') { + flags |= PF_FLAG_SPACE_SIGN; + } else if (*str == '#') { + alt = PF_FLAG_SHOW_PREFIX; + } else if (*str == '0') { + flags |= PF_FLAG_PAD_AFTER_SIGN; + fill = '0'; + } else { + break; + } + str++; + } + // parse width, if it exists + int width = 0; + if (str < top) { + if (*str == '*') { + if (arg_i >= n_args) { + goto not_enough_args; + } + width = mp_obj_get_int(args[arg_i++]); + str++; + } else { + str = (const byte *)str_to_int((const char *)str, (const char *)top, &width); + } + } + int prec = -1; + if (str < top && *str == '.') { + if (++str < top) { + if (*str == '*') { + if (arg_i >= n_args) { + goto not_enough_args; + } + prec = mp_obj_get_int(args[arg_i++]); + str++; + } else { + prec = 0; + str = (const byte *)str_to_int((const char *)str, (const char *)top, &prec); + } + } + } + + if (str >= top) { + incomplete_format: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return terse_str_format_value_error(); + #else + mp_raise_ValueError(MP_ERROR_TEXT("incomplete format")); + #endif + } + + // Tuple value lookup + if (arg == MP_OBJ_NULL) { + if (arg_i >= n_args) { +not_enough_args: + mp_raise_TypeError(MP_ERROR_TEXT("format string needs more arguments")); + } + arg = args[arg_i++]; + } + switch (*str) { + case 'c': + if (mp_obj_is_str(arg)) { + size_t slen; + const char *s = mp_obj_str_get_data(arg, &slen); + if (slen != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("%%c needs int or char") ); + } + mp_print_strn(&print, s, 1, flags, ' ', width); + } else if (arg_looks_integer(arg)) { + char ch = mp_obj_get_int(arg); + mp_print_strn(&print, &ch, 1, flags, ' ', width); + } else { + mp_raise_TypeError(MP_ERROR_TEXT("integer needed")); + } + break; + + case 'd': + case 'i': + case 'u': + mp_print_mp_int(&print, arg_as_int(arg), 10, 'a', flags, fill, width, prec); + break; + + #if MICROPY_PY_BUILTINS_FLOAT + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + mp_print_float(&print, mp_obj_get_float(arg), *str, flags, fill, width, prec); + break; + #endif + + case 'o': + if (alt) { + flags |= (PF_FLAG_SHOW_PREFIX | PF_FLAG_SHOW_OCTAL_LETTER); + } + mp_print_mp_int(&print, arg, 8, 'a', flags, fill, width, prec); + break; + + case 'r': + case 's': { + vstr_t arg_vstr; + mp_print_t arg_print; + vstr_init_print(&arg_vstr, 16, &arg_print); + mp_print_kind_t print_kind = (*str == 'r' ? PRINT_REPR : PRINT_STR); + if (print_kind == PRINT_STR && is_bytes && mp_obj_is_type(arg, &mp_type_bytes)) { + // If we have something like b"%s" % b"1", bytes arg should be + // printed undecorated. + print_kind = PRINT_RAW; + } + mp_obj_print_helper(&arg_print, arg, print_kind); + uint vlen = arg_vstr.len; + if (prec < 0) { + prec = vlen; + } + if (vlen > (uint)prec) { + vlen = prec; + } + mp_print_strn(&print, arg_vstr.buf, vlen, flags, ' ', width); + vstr_clear(&arg_vstr); + break; + } + + case 'X': + case 'x': + mp_print_mp_int(&print, arg, 16, *str - ('X' - 'A'), flags | alt, fill, width, prec); + break; + + default: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return terse_str_format_value_error(); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("unsupported format character '%c' (0x%x) at index %d"), + *str, *str, str - start_str)); + #endif + } + } + + if (arg_i != n_args) { + mp_raise_TypeError(MP_ERROR_TEXT("format string didn't convert all arguments")); + } + + return mp_obj_new_str_from_vstr(is_bytes ? &mp_type_bytes : &mp_type_str, &vstr); +} +#endif + +// The implementation is optimized, returning the original string if there's +// nothing to replace. +STATIC mp_obj_t str_replace(size_t n_args, const mp_obj_t *args) { + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + mp_int_t max_rep = -1; + if (n_args == 4) { + max_rep = mp_obj_get_int(args[3]); + if (max_rep == 0) { + return args[0]; + } else if (max_rep < 0) { + max_rep = -1; + } + } + + // if max_rep is still -1 by this point we will need to do all possible replacements + + // check argument types + + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + + if (mp_obj_get_type(args[2]) != self_type) { + return bad_implicit_conversion(args[2]); + } + + // extract string data + + GET_STR_DATA_LEN(args[0], str, str_len); + GET_STR_DATA_LEN(args[1], old, old_len); + GET_STR_DATA_LEN(args[2], new, new_len); + + // old won't exist in str if it's longer, so nothing to replace + if (old_len > str_len) { + return args[0]; + } + + // data for the replaced string + byte *data = NULL; + vstr_t vstr; + + // do 2 passes over the string: + // first pass computes the required length of the replaced string + // second pass does the replacements + for (;;) { + size_t replaced_str_index = 0; + size_t num_replacements_done = 0; + const byte *old_occurrence; + const byte *offset_ptr = str; + size_t str_len_remain = str_len; + if (old_len == 0) { + // if old_str is empty, copy new_str to start of replaced string + // copy the replacement string + if (data != NULL) { + memcpy(data, new, new_len); + } + replaced_str_index += new_len; + num_replacements_done++; + } + while (num_replacements_done != (size_t)max_rep && str_len_remain > 0 && (old_occurrence = find_subbytes(offset_ptr, str_len_remain, old, old_len, 1)) != NULL) { + if (old_len == 0) { + old_occurrence += 1; + } + // copy from just after end of last occurrence of to-be-replaced string to right before start of next occurrence + if (data != NULL) { + memcpy(data + replaced_str_index, offset_ptr, old_occurrence - offset_ptr); + } + replaced_str_index += old_occurrence - offset_ptr; + // copy the replacement string + if (data != NULL) { + memcpy(data + replaced_str_index, new, new_len); + } + replaced_str_index += new_len; + offset_ptr = old_occurrence + old_len; + str_len_remain = str + str_len - offset_ptr; + num_replacements_done++; + } + + // copy from just after end of last occurrence of to-be-replaced string to end of old string + if (data != NULL) { + memcpy(data + replaced_str_index, offset_ptr, str_len_remain); + } + replaced_str_index += str_len_remain; + + if (data == NULL) { + // first pass + if (num_replacements_done == 0) { + // no substr found, return original string + return args[0]; + } else { + // substr found, allocate new string + vstr_init_len(&vstr, replaced_str_index); + data = (byte *)vstr.buf; + assert(data != NULL); + } + } else { + // second pass, we are done + break; + } + } + + return mp_obj_new_str_from_vstr(self_type, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_replace_obj, 3, 4, str_replace); + +#if MICROPY_PY_BUILTINS_STR_COUNT +STATIC mp_obj_t str_count(size_t n_args, const mp_obj_t *args) { + const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); + mp_check_self(mp_obj_is_str_or_bytes(args[0])); + + // check argument type + if (mp_obj_get_type(args[1]) != self_type) { + return bad_implicit_conversion(args[1]); + } + + GET_STR_DATA_LEN(args[0], haystack, haystack_len); + GET_STR_DATA_LEN(args[1], needle, needle_len); + + const byte *start = haystack; + const byte *end = haystack + haystack_len; + if (n_args >= 3 && args[2] != mp_const_none) { + start = str_index_to_ptr(self_type, haystack, haystack_len, args[2], true); + } + if (n_args >= 4 && args[3] != mp_const_none) { + end = str_index_to_ptr(self_type, haystack, haystack_len, args[3], true); + } + + // if needle_len is zero then we count each gap between characters as an occurrence + if (needle_len == 0) { + return MP_OBJ_NEW_SMALL_INT(utf8_charlen(start, end - start) + 1); + } + + // count the occurrences + mp_int_t num_occurrences = 0; + for (const byte *haystack_ptr = start; haystack_ptr + needle_len <= end;) { + if (memcmp(haystack_ptr, needle, needle_len) == 0) { + num_occurrences++; + haystack_ptr += needle_len; + } else { + haystack_ptr = utf8_next_char(haystack_ptr); + } + } + + return MP_OBJ_NEW_SMALL_INT(num_occurrences); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_count_obj, 2, 4, str_count); +#endif + +#if MICROPY_PY_BUILTINS_STR_PARTITION +STATIC mp_obj_t str_partitioner(mp_obj_t self_in, mp_obj_t arg, int direction) { + mp_check_self(mp_obj_is_str_or_bytes(self_in)); + const mp_obj_type_t *self_type = mp_obj_get_type(self_in); + if (self_type != mp_obj_get_type(arg)) { + return bad_implicit_conversion(arg); + } + + GET_STR_DATA_LEN(self_in, str, str_len); + GET_STR_DATA_LEN(arg, sep, sep_len); + + if (sep_len == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("empty separator")); + } + + mp_obj_t result[3]; + if (self_type == &mp_type_str) { + result[0] = MP_OBJ_NEW_QSTR(MP_QSTR_); + result[1] = MP_OBJ_NEW_QSTR(MP_QSTR_); + result[2] = MP_OBJ_NEW_QSTR(MP_QSTR_); + } else { + result[0] = mp_const_empty_bytes; + result[1] = mp_const_empty_bytes; + result[2] = mp_const_empty_bytes; + } + + if (direction > 0) { + result[0] = self_in; + } else { + result[2] = self_in; + } + + const byte *position_ptr = find_subbytes(str, str_len, sep, sep_len, direction); + if (position_ptr != NULL) { + size_t position = position_ptr - str; + result[0] = mp_obj_new_str_of_type(self_type, str, position); + result[1] = arg; + result[2] = mp_obj_new_str_of_type(self_type, str + position + sep_len, str_len - position - sep_len); + } + + return mp_obj_new_tuple(3, result); +} + +STATIC mp_obj_t str_partition(mp_obj_t self_in, mp_obj_t arg) { + return str_partitioner(self_in, arg, 1); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_partition_obj, str_partition); + +STATIC mp_obj_t str_rpartition(mp_obj_t self_in, mp_obj_t arg) { + return str_partitioner(self_in, arg, -1); +} +MP_DEFINE_CONST_FUN_OBJ_2(str_rpartition_obj, str_rpartition); +#endif + +// Supposedly not too critical operations, so optimize for code size +STATIC mp_obj_t str_caseconv(unichar (*op)(unichar), mp_obj_t self_in) { + GET_STR_DATA_LEN(self_in, self_data, self_len); + vstr_t vstr; + vstr_init_len(&vstr, self_len); + byte *data = (byte *)vstr.buf; + for (size_t i = 0; i < self_len; i++) { + *data++ = op(*self_data++); + } + return mp_obj_new_str_from_vstr(mp_obj_get_type(self_in), &vstr); +} + +STATIC mp_obj_t str_lower(mp_obj_t self_in) { + return str_caseconv(unichar_tolower, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_lower_obj, str_lower); + +STATIC mp_obj_t str_upper(mp_obj_t self_in) { + return str_caseconv(unichar_toupper, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_upper_obj, str_upper); + +STATIC mp_obj_t str_uni_istype(bool (*f)(unichar), mp_obj_t self_in) { + GET_STR_DATA_LEN(self_in, self_data, self_len); + + if (self_len == 0) { + return mp_const_false; // default to False for empty str + } + + if (f != unichar_isupper && f != unichar_islower) { + for (size_t i = 0; i < self_len; i++) { + if (!f(*self_data++)) { + return mp_const_false; + } + } + } else { + bool contains_alpha = false; + + for (size_t i = 0; i < self_len; i++) { // only check alphanumeric characters + if (unichar_isalpha(*self_data++)) { + contains_alpha = true; + if (!f(*(self_data - 1))) { // -1 because we already incremented above + return mp_const_false; + } + } + } + + if (!contains_alpha) { + return mp_const_false; + } + } + + return mp_const_true; +} + +STATIC mp_obj_t str_isspace(mp_obj_t self_in) { + return str_uni_istype(unichar_isspace, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isspace_obj, str_isspace); + +STATIC mp_obj_t str_isalpha(mp_obj_t self_in) { + return str_uni_istype(unichar_isalpha, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isalpha_obj, str_isalpha); + +STATIC mp_obj_t str_isdigit(mp_obj_t self_in) { + return str_uni_istype(unichar_isdigit, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isdigit_obj, str_isdigit); + +STATIC mp_obj_t str_isupper(mp_obj_t self_in) { + return str_uni_istype(unichar_isupper, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_isupper_obj, str_isupper); + +STATIC mp_obj_t str_islower(mp_obj_t self_in) { + return str_uni_istype(unichar_islower, self_in); +} +MP_DEFINE_CONST_FUN_OBJ_1(str_islower_obj, str_islower); + +#if MICROPY_CPYTHON_COMPAT +// These methods are superfluous in the presence of str() and bytes() +// constructors. +// TODO: should accept kwargs too +STATIC mp_obj_t bytes_decode(size_t n_args, const mp_obj_t *args) { + mp_obj_t new_args[2]; + if (n_args == 1) { + new_args[0] = args[0]; + new_args[1] = MP_OBJ_NEW_QSTR(MP_QSTR_utf_hyphen_8); + args = new_args; + n_args++; + } + return mp_obj_str_make_new(&mp_type_str, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bytes_decode_obj, 1, 3, bytes_decode); + +// TODO: should accept kwargs too +STATIC mp_obj_t str_encode(size_t n_args, const mp_obj_t *args) { + mp_obj_t new_args[2]; + if (n_args == 1) { + new_args[0] = args[0]; + new_args[1] = MP_OBJ_NEW_QSTR(MP_QSTR_utf_hyphen_8); + args = new_args; + n_args++; + } + return bytes_make_new(NULL, n_args, 0, args); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_encode_obj, 1, 3, str_encode); +#endif + +mp_int_t mp_obj_str_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + if (flags == MP_BUFFER_READ) { + GET_STR_DATA_LEN(self_in, str_data, str_len); + bufinfo->buf = (void *)str_data; + bufinfo->len = str_len; + bufinfo->typecode = 'B'; // bytes should be unsigned, so should unicode byte-access + return 0; + } else { + // can't write to a string + return 1; + } +} + +STATIC const mp_rom_map_elem_t str8_locals_dict_table[] = { + #if MICROPY_CPYTHON_COMPAT + { MP_ROM_QSTR(MP_QSTR_decode), MP_ROM_PTR(&bytes_decode_obj) }, + #if !MICROPY_PY_BUILTINS_STR_UNICODE + // If we have separate unicode type, then here we have methods only + // for bytes type, and it should not have encode() methods. Otherwise, + // we have non-compliant-but-practical bytestring type, which shares + // method table with bytes, so they both have encode() and decode() + // methods (which should do type checking at runtime). + { MP_ROM_QSTR(MP_QSTR_encode), MP_ROM_PTR(&str_encode_obj) }, + #endif + #endif + { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&str_find_obj) }, + { MP_ROM_QSTR(MP_QSTR_rfind), MP_ROM_PTR(&str_rfind_obj) }, + { MP_ROM_QSTR(MP_QSTR_index), MP_ROM_PTR(&str_index_obj) }, + { MP_ROM_QSTR(MP_QSTR_rindex), MP_ROM_PTR(&str_rindex_obj) }, + { MP_ROM_QSTR(MP_QSTR_join), MP_ROM_PTR(&str_join_obj) }, + { MP_ROM_QSTR(MP_QSTR_split), MP_ROM_PTR(&str_split_obj) }, + #if MICROPY_PY_BUILTINS_STR_SPLITLINES + { MP_ROM_QSTR(MP_QSTR_splitlines), MP_ROM_PTR(&str_splitlines_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_rsplit), MP_ROM_PTR(&str_rsplit_obj) }, + { MP_ROM_QSTR(MP_QSTR_startswith), MP_ROM_PTR(&str_startswith_obj) }, + { MP_ROM_QSTR(MP_QSTR_endswith), MP_ROM_PTR(&str_endswith_obj) }, + { MP_ROM_QSTR(MP_QSTR_strip), MP_ROM_PTR(&str_strip_obj) }, + { MP_ROM_QSTR(MP_QSTR_lstrip), MP_ROM_PTR(&str_lstrip_obj) }, + { MP_ROM_QSTR(MP_QSTR_rstrip), MP_ROM_PTR(&str_rstrip_obj) }, + { MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&str_format_obj) }, + { MP_ROM_QSTR(MP_QSTR_replace), MP_ROM_PTR(&str_replace_obj) }, + #if MICROPY_PY_BUILTINS_STR_COUNT + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&str_count_obj) }, + #endif + #if MICROPY_PY_BUILTINS_STR_PARTITION + { MP_ROM_QSTR(MP_QSTR_partition), MP_ROM_PTR(&str_partition_obj) }, + { MP_ROM_QSTR(MP_QSTR_rpartition), MP_ROM_PTR(&str_rpartition_obj) }, + #endif + #if MICROPY_PY_BUILTINS_STR_CENTER + { MP_ROM_QSTR(MP_QSTR_center), MP_ROM_PTR(&str_center_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_lower), MP_ROM_PTR(&str_lower_obj) }, + { MP_ROM_QSTR(MP_QSTR_upper), MP_ROM_PTR(&str_upper_obj) }, + { MP_ROM_QSTR(MP_QSTR_isspace), MP_ROM_PTR(&str_isspace_obj) }, + { MP_ROM_QSTR(MP_QSTR_isalpha), MP_ROM_PTR(&str_isalpha_obj) }, + { MP_ROM_QSTR(MP_QSTR_isdigit), MP_ROM_PTR(&str_isdigit_obj) }, + { MP_ROM_QSTR(MP_QSTR_isupper), MP_ROM_PTR(&str_isupper_obj) }, + { MP_ROM_QSTR(MP_QSTR_islower), MP_ROM_PTR(&str_islower_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(str8_locals_dict, str8_locals_dict_table); + +#if !MICROPY_PY_BUILTINS_STR_UNICODE +STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); + +const mp_obj_type_t mp_type_str = { + { &mp_type_type }, + .name = MP_QSTR_str, + .print = str_print, + .make_new = mp_obj_str_make_new, + .binary_op = mp_obj_str_binary_op, + .subscr = bytes_subscr, + .getiter = mp_obj_new_str_iterator, + .buffer_p = { .get_buffer = mp_obj_str_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&str8_locals_dict, +}; +#endif + +// Reuses most of methods from str +const mp_obj_type_t mp_type_bytes = { + { &mp_type_type }, + .name = MP_QSTR_bytes, + .print = str_print, + .make_new = bytes_make_new, + .binary_op = mp_obj_str_binary_op, + .subscr = bytes_subscr, + .getiter = mp_obj_new_bytes_iterator, + .buffer_p = { .get_buffer = mp_obj_str_get_buffer }, + .locals_dict = (mp_obj_dict_t *)&str8_locals_dict, +}; + +// The zero-length bytes object, with data that includes a null-terminating byte +const mp_obj_str_t mp_const_empty_bytes_obj = {{&mp_type_bytes}, 0, 0, (const byte *)""}; + +// Create a str/bytes object using the given data. New memory is allocated and +// the data is copied across. This function should only be used if the type is bytes, +// or if the type is str and the string data is known to be not interned. +mp_obj_t mp_obj_new_str_copy(const mp_obj_type_t *type, const byte *data, size_t len) { + mp_obj_str_t *o = m_new_obj(mp_obj_str_t); + o->base.type = type; + o->len = len; + if (data) { + o->hash = qstr_compute_hash(data, len); + byte *p = m_new(byte, len + 1); + o->data = p; + memcpy(p, data, len * sizeof(byte)); + p[len] = '\0'; // for now we add null for compatibility with C ASCIIZ strings + } + return MP_OBJ_FROM_PTR(o); +} + +// Create a str/bytes object using the given data. If the type is str and the string +// data is already interned, then a qstr object is returned. Otherwise new memory is +// allocated for the object and the data is copied across. +mp_obj_t mp_obj_new_str_of_type(const mp_obj_type_t *type, const byte *data, size_t len) { + if (type == &mp_type_str) { + return mp_obj_new_str((const char *)data, len); + } else { + return mp_obj_new_bytes(data, len); + } +} + +// Create a str using a qstr to store the data; may use existing or new qstr. +mp_obj_t mp_obj_new_str_via_qstr(const char *data, size_t len) { + return MP_OBJ_NEW_QSTR(qstr_from_strn(data, len)); +} + +// Create a str/bytes object from the given vstr. The vstr buffer is resized to +// the exact length required and then reused for the str/bytes object. The vstr +// is cleared and can safely be passed to vstr_free if it was heap allocated. +mp_obj_t mp_obj_new_str_from_vstr(const mp_obj_type_t *type, vstr_t *vstr) { + // if not a bytes object, look if a qstr with this data already exists + if (type == &mp_type_str) { + qstr q = qstr_find_strn(vstr->buf, vstr->len); + if (q != MP_QSTRnull) { + vstr_clear(vstr); + vstr->alloc = 0; + return MP_OBJ_NEW_QSTR(q); + } + } + + // make a new str/bytes object + mp_obj_str_t *o = m_new_obj(mp_obj_str_t); + if (o == NULL) { + return MP_OBJ_NULL; + } + o->base.type = type; + o->len = vstr->len; + o->hash = qstr_compute_hash((byte*)vstr->buf, vstr->len); + if (vstr->len + 1 == vstr->alloc) { + o->data = (byte*)vstr->buf; + } else { + o->data = (byte*)m_renew(char, vstr->buf, vstr->alloc, vstr->len + 1); + if (o->data == NULL) { + return MP_OBJ_NULL; + } + } + ((byte*)o->data)[o->len] = '\0'; // add null byte + vstr->buf = NULL; + vstr->alloc = 0; + return MP_OBJ_FROM_PTR(o); +} + +mp_obj_t mp_obj_new_str(const char * data, size_t len) { + qstr q = qstr_find_strn(data, len); + if (q != MP_QSTRnull) { + // qstr with this data already exists + return MP_OBJ_NEW_QSTR(q); + } else { + // no existing qstr, don't make one + return mp_obj_new_str_copy(&mp_type_str, (const byte*)data, len); + } +} + +mp_obj_t mp_obj_str_intern(mp_obj_t str) { + GET_STR_DATA_LEN(str, data, len); + return mp_obj_new_str_via_qstr((const char *)data, len); +} + +mp_obj_t mp_obj_str_intern_checked(mp_obj_t obj) { + size_t len; + const char *data = mp_obj_str_get_data(obj, &len); + if (data == NULL) { + // exception + return MP_OBJ_NULL; + } + return mp_obj_new_str_via_qstr((const char *)data, len); +} + +mp_obj_t mp_obj_new_bytes(const byte* data, size_t len) { + return mp_obj_new_str_copy(&mp_type_bytes, data, len); +} + +bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) { + if (mp_obj_is_qstr(s1) && mp_obj_is_qstr(s2)) { + return s1 == s2; + } else { + GET_STR_HASH(s1, h1); + GET_STR_HASH(s2, h2); + // If any of hashes is 0, it means it's not valid + if (h1 != 0 && h2 != 0 && h1 != h2) { + return false; + } + GET_STR_DATA_LEN(s1, d1, l1); + GET_STR_DATA_LEN(s2, d2, l2); + if (l1 != l2) { + return false; + } + return memcmp(d1, d2, l1) == 0; + } +} + +STATIC mp_obj_t bad_implicit_conversion(mp_obj_t self_in) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to str implicitly")); + #else + const qstr src_name = mp_obj_get_type(self_in)->name; + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("can't convert '%q' object to %q implicitly"), + src_name, src_name == MP_QSTR_str ? MP_QSTR_bytes : MP_QSTR_str)); + #endif +} + +// use this if you will anyway convert the string to a qstr +// will be more efficient for the case where it's already a qstr +qstr mp_obj_str_get_qstr(mp_obj_t self_in) { + if (mp_obj_is_qstr(self_in)) { + return MP_OBJ_QSTR_VALUE(self_in); + } else if (mp_obj_is_type(self_in, &mp_type_str)) { + mp_obj_str_t *self = MP_OBJ_TO_PTR(self_in); + return qstr_from_strn((char *)self->data, self->len); + } else { + bad_implicit_conversion(self_in); + return MP_QSTR_NULL; // TODO callers should handle this case + } +} + +// only use this function if you need the str data to be zero terminated +// at the moment all strings are zero terminated to help with C ASCIIZ compatibility +const char *mp_obj_str_get_str(mp_obj_t self_in) { + if (mp_obj_is_str_or_bytes(self_in)) { + GET_STR_DATA_LEN(self_in, s, l); + (void)l; // len unused + return (const char *)s; + } else { + bad_implicit_conversion(self_in); + return NULL; // TODO callers should handle this case + } +} + +const char *mp_obj_str_get_data(mp_obj_t self_in, size_t *len) { + if (mp_obj_is_str_or_bytes(self_in)) { + GET_STR_DATA_LEN(self_in, s, l); + *len = l; + return (const char *)s; + } else { + bad_implicit_conversion(self_in); + return NULL; // TODO callers should handle this case + } +} + +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D +const byte *mp_obj_str_get_data_no_check(mp_obj_t self_in, size_t *len) { + if (mp_obj_is_qstr(self_in)) { + return qstr_data(MP_OBJ_QSTR_VALUE(self_in), len); + } else { + *len = ((mp_obj_str_t *)MP_OBJ_TO_PTR(self_in))->len; + return ((mp_obj_str_t *)MP_OBJ_TO_PTR(self_in))->data; + } +} +#endif + +/******************************************************************************/ +/* str iterator */ + +typedef struct _mp_obj_str8_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t str; + size_t cur; +} mp_obj_str8_it_t; + +#if !MICROPY_PY_BUILTINS_STR_UNICODE +STATIC mp_obj_t str_it_iternext(mp_obj_t self_in) { + mp_obj_str8_it_t *self = MP_OBJ_TO_PTR(self_in); + GET_STR_DATA_LEN(self->str, str, len); + if (self->cur < len) { + mp_obj_t o_out = mp_obj_new_str_via_qstr((const char *)str + self->cur, 1); + self->cur += 1; + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_str8_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_str8_it_t *o = (mp_obj_str8_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = str_it_iternext; + o->str = str; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} +#endif + +STATIC mp_obj_t bytes_it_iternext(mp_obj_t self_in) { + mp_obj_str8_it_t *self = MP_OBJ_TO_PTR(self_in); + GET_STR_DATA_LEN(self->str, str, len); + if (self->cur < len) { + mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT(str[self->cur]); + self->cur += 1; + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_str8_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_str8_it_t *o = (mp_obj_str8_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = bytes_it_iternext; + o->str = str; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} diff --git a/ports/wapy/qstrdefsport.h b/ports/wapy/qstrdefsport.h new file mode 100644 index 000000000..010b55d19 --- /dev/null +++ b/ports/wapy/qstrdefsport.h @@ -0,0 +1,51 @@ +// qstrs specific to this port +Q(\r\n) +Q(__aenter__) +Q(__aexit__) +Q(__nonzero__) +Q(bound_method) +Q(pend_throw) +Q(__bool__) +Q(__pos__) +Q(__invert_) +Q(__abs__) +Q(__sizeof__) +Q(__eq__) +Q(__neg__) +Q(__lt__) +Q(__gt__) +Q(__le__) +Q(__ge__) +Q(__iadd__) +Q(__isub__) +Q(file) +Q(mode) +Q(encoding) +Q(r) +Q(buffering) +Q(heap_lock) +Q(heap_unlock) +Q(ld) +Q(unref) +Q(ffi) +Q(ptr) +Q(use) +Q(srv) +Q(config) +Q(oref) +Q(µO|%s|%s) +Q(android) +Q(emscripten) +Q(window) +Q(document) +Q(canvas) +Q(width) +Q(height) +Q(title) +Q(crt) +Q(set_x) +Q(set_z) +Q(set_text) + + + diff --git a/ports/wapy/repl.c b/ports/wapy/repl.c new file mode 100644 index 000000000..4f7b0b19e --- /dev/null +++ b/ports/wapy/repl.c @@ -0,0 +1,240 @@ + + +typedef struct _repl_t { + // This structure originally also held current REPL line, + // but it was moved to MP_STATE_VM(repl_line) as containing + // root pointer. Still keep structure in case more state + // will be added later. + //vstr_t line; + bool cont_line; + bool paste_mode; +} repl_t; + + + +uint8_t pyexec_repl_active; +repl_t repl; + + +//int pyexec_repl_repl_restart(int ret); + + +#include "lib/mp-readline/readline.h" + +int pyexec_raw_repl_process_char(int c); + + +int pyexec_repl_repl_restart(int ret) { + if ((ret)>=0) { + vstr_reset(MP_STATE_VM(repl_line)); + repl.cont_line = false; + repl.paste_mode = false; + readline_init(MP_STATE_VM(repl_line), ">>> "); + } + return ret; +} + +//STATIC +int pyexec_friendly_repl_process_char(int c) { + int ret = 0; + + if (repl.paste_mode) { + if (c == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (c == CHAR_CTRL_D) { + // end of input + mp_hal_stdout_tx_str("\r\n"); + + ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + goto input_restart; + } else { + // add char to buffer and echo + vstr_add_byte(MP_STATE_VM(repl_line), c); + if (c == '\r') { + mp_hal_stdout_tx_str("\r\n=== "); + } else { + char buf[1] = {c}; + mp_hal_stdout_tx_strn(buf, 1); + } + return 0; + } + } + +// int + ret = readline_process_char(c); + + if (!repl.cont_line) { + + if (ret == CHAR_CTRL_A) { + // change to raw REPL + pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; + mp_hal_stdout_tx_str("\r\n"); + pyexec_raw_repl_process_char(CHAR_CTRL_A); + return 0; + } else if (ret == CHAR_CTRL_B) { + // reset friendly REPL + mp_hal_stdout_tx_str("\r\n"); + 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 + mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); + #endif + goto input_restart; + } else if (ret == CHAR_CTRL_C) { + // break + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(MP_STATE_VM(repl_line)); + return PYEXEC_FORCED_EXIT; + } else if (ret == CHAR_CTRL_E) { + // paste mode + mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); + vstr_reset(MP_STATE_VM(repl_line)); + repl.paste_mode = true; + return 0; + } + + if (ret < 0) { + return 0; + } + + if (!mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { + goto exec; + } + + vstr_add_byte(MP_STATE_VM(repl_line), '\n'); + repl.cont_line = true; + readline_note_newline("... "); + return 0; + + } else { + + if (ret == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + repl.cont_line = false; + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // stop entering compound statement + goto exec; + } + + if (ret < 0) { + return 0; + } + + if (mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { + vstr_add_byte(MP_STATE_VM(repl_line), '\n'); + readline_note_newline("... "); + return 0; + } + +exec: ; +#if 1 + char *data = MP_STATE_VM(repl_line)->buf; +// int dlen = MP_STATE_VM(repl_line)->len; + + if (i_main.shm_stdio) { + strcpy( i_main.shm_stdio, data ); + return -1; + } else + cdbg("463: REPL space not allocated!"); + +#else + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_SINGLE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } +#endif + +input_restart: + return pyexec_repl_repl_restart(ret); + } +} + + + +//STATIC +int pyexec_raw_repl_process_char(int c) { + if (c == CHAR_CTRL_A) { + // reset raw REPL + mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); + goto reset; + } else if (c == CHAR_CTRL_B) { + // change to friendly REPL + pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; + vstr_reset(MP_STATE_VM(repl_line)); + repl.cont_line = false; + repl.paste_mode = false; + pyexec_friendly_repl_process_char(CHAR_CTRL_B); + return 0; + } else if (c == CHAR_CTRL_C) { + // clear line + vstr_reset(MP_STATE_VM(repl_line)); + return 0; + } else if (c == CHAR_CTRL_D) { + // input finished + } else { + // let through any other raw 8-bit value + vstr_add_byte(MP_STATE_VM(repl_line), c); + return 0; + } + + // indicate reception of command + mp_hal_stdout_tx_str("OK"); + + if (MP_STATE_VM(repl_line)->len == 0) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(MP_STATE_VM(repl_line)); + return PYEXEC_FORCED_EXIT; + } + + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + +reset: + vstr_reset(MP_STATE_VM(repl_line)); + mp_hal_stdout_tx_str(">"); + + return 0; +} + + + + +int pyexec_event_repl_process_char(int c) { + pyexec_repl_active = 1; + int res; + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + res = pyexec_raw_repl_process_char(c); + } else { + res = pyexec_friendly_repl_process_char(c); + } + pyexec_repl_active = 0; + return res; +} + + + +void pyexec_event_repl_init(void) { + MP_STATE_VM(repl_line) = vstr_new(32); + repl.cont_line = false; + repl.paste_mode = false; + // no prompt before printing friendly REPL banner or entering raw REPL + readline_init(MP_STATE_VM(repl_line), ""); + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + pyexec_raw_repl_process_char(CHAR_CTRL_A); + } else { + pyexec_friendly_repl_process_char(CHAR_CTRL_B); + } +} diff --git a/ports/wapy/repl_nlr.c b/ports/wapy/repl_nlr.c new file mode 100644 index 000000000..7317f5613 --- /dev/null +++ b/ports/wapy/repl_nlr.c @@ -0,0 +1,200 @@ + +typedef struct _repl_t { + // This structure originally also held current REPL line, + // but it was moved to MP_STATE_VM(repl_line) as containing + // root pointer. Still keep structure in case more state + // will be added later. + // vstr_t line; + bool cont_line; + bool paste_mode; +} repl_t; + +repl_t repl; + +STATIC int pyexec_raw_repl_process_char(int c); +STATIC int pyexec_friendly_repl_process_char(int c); + +void pyexec_event_repl_init(void) { + MP_STATE_VM(repl_line) = vstr_new(32); + repl.cont_line = false; + repl.paste_mode = false; + // no prompt before printing friendly REPL banner or entering raw REPL + readline_init(MP_STATE_VM(repl_line), ""); + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + pyexec_raw_repl_process_char(CHAR_CTRL_A); + } else { + pyexec_friendly_repl_process_char(CHAR_CTRL_B); + } +} + +STATIC int pyexec_raw_repl_process_char(int c) { + if (c == CHAR_CTRL_A) { + // reset raw REPL + mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); + goto reset; + } else if (c == CHAR_CTRL_B) { + // change to friendly REPL + pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; + vstr_reset(MP_STATE_VM(repl_line)); + repl.cont_line = false; + repl.paste_mode = false; + pyexec_friendly_repl_process_char(CHAR_CTRL_B); + return 0; + } else if (c == CHAR_CTRL_C) { + // clear line + vstr_reset(MP_STATE_VM(repl_line)); + return 0; + } else if (c == CHAR_CTRL_D) { + // input finished + } else { + // let through any other raw 8-bit value + vstr_add_byte(MP_STATE_VM(repl_line), c); + return 0; + } + + // indicate reception of command + mp_hal_stdout_tx_str("OK"); + + if (MP_STATE_VM(repl_line)->len == 0) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(MP_STATE_VM(repl_line)); + return PYEXEC_FORCED_EXIT; + } + + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + +reset: + vstr_reset(MP_STATE_VM(repl_line)); + mp_hal_stdout_tx_str(">"); + + return 0; +} + +STATIC int pyexec_friendly_repl_process_char(int c) { + if (repl.paste_mode) { + if (c == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (c == CHAR_CTRL_D) { + // end of input + mp_hal_stdout_tx_str("\r\n"); + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + goto input_restart; + } else { + // add char to buffer and echo + vstr_add_byte(MP_STATE_VM(repl_line), c); + if (c == '\r') { + mp_hal_stdout_tx_str("\r\n=== "); + } else { + char buf[1] = {c}; + mp_hal_stdout_tx_strn(buf, 1); + } + return 0; + } + } + + int ret = readline_process_char(c); + + if (!repl.cont_line) { + + if (ret == CHAR_CTRL_A) { + // change to raw REPL + pyexec_mode_kind = PYEXEC_MODE_RAW_REPL; + mp_hal_stdout_tx_str("\r\n"); + pyexec_raw_repl_process_char(CHAR_CTRL_A); + return 0; + } else if (ret == CHAR_CTRL_B) { + // reset friendly REPL + mp_hal_stdout_tx_str("\r\n"); + 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 + mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n"); + #endif + goto input_restart; + } else if (ret == CHAR_CTRL_C) { + // break + mp_hal_stdout_tx_str("\r\n"); + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // exit for a soft reset + mp_hal_stdout_tx_str("\r\n"); + vstr_clear(MP_STATE_VM(repl_line)); + return PYEXEC_FORCED_EXIT; + } else if (ret == CHAR_CTRL_E) { + // paste mode + mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "); + vstr_reset(MP_STATE_VM(repl_line)); + repl.paste_mode = true; + return 0; + } + + if (ret < 0) { + return 0; + } + + if (!mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { + goto exec; + } + + vstr_add_byte(MP_STATE_VM(repl_line), '\n'); + repl.cont_line = true; + readline_note_newline("... "); + return 0; + + } else { + + if (ret == CHAR_CTRL_C) { + // cancel everything + mp_hal_stdout_tx_str("\r\n"); + repl.cont_line = false; + goto input_restart; + } else if (ret == CHAR_CTRL_D) { + // stop entering compound statement + goto exec; + } + + if (ret < 0) { + return 0; + } + + if (mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) { + vstr_add_byte(MP_STATE_VM(repl_line), '\n'); + readline_note_newline("... "); + return 0; + } + + exec:; + int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_SINGLE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + + input_restart: + vstr_reset(MP_STATE_VM(repl_line)); + repl.cont_line = false; + repl.paste_mode = false; + readline_init(MP_STATE_VM(repl_line), ">>> "); + return 0; + } +} + +uint8_t pyexec_repl_active; +int pyexec_event_repl_process_char(int c) { + pyexec_repl_active = 1; + int res; + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + res = pyexec_raw_repl_process_char(c); + } else { + res = pyexec_friendly_repl_process_char(c); + } + pyexec_repl_active = 0; + return res; +} diff --git a/ports/wapy/runtime_no_nlr.c b/ports/wapy/runtime_no_nlr.c new file mode 100644 index 000000000..2ee3c4d86 --- /dev/null +++ b/ports/wapy/runtime_no_nlr.c @@ -0,0 +1,1694 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2014-2018 Paul Sokolovsky + * + * 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 "py/parsenum.h" +#include "py/compile.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objlist.h" +#include "py/objtype.h" +#include "py/objmodule.h" +#include "py/objgenerator.h" +#include "py/smallint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stackctrl.h" +#include "py/gc.h" + +#include "upython.h" + +#if MICROPY_DEBUG_VERBOSE // print debugging info +#define DEBUG_PRINT (1) +#define DEBUG_printf DEBUG_printf +#define DEBUG_OP_printf(...) DEBUG_printf(__VA_ARGS__) +#else // don't print debugging info +#define DEBUG_printf(...) (void)0 +#define DEBUG_OP_printf(...) (void)0 +#endif + +const mp_obj_module_t mp_module___main__ = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&MP_STATE_VM(dict_main), +}; + +void mp_init(void) { + MP_STATE_THREAD(active_exception) = NULL; + + qstr_init(); + + // no pending exceptions to start with + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + #if MICROPY_ENABLE_SCHEDULER + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; + MP_STATE_VM(sched_idx) = 0; + MP_STATE_VM(sched_len) = 0; + #endif + + #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF + mp_init_emergency_exception_buf(); + #endif + + #if MICROPY_KBD_EXCEPTION + // initialise the exception object for raising KeyboardInterrupt + MP_STATE_VM(mp_kbd_exception).base.type = &mp_type_KeyboardInterrupt; + MP_STATE_VM(mp_kbd_exception).traceback_alloc = 0; + MP_STATE_VM(mp_kbd_exception).traceback_len = 0; + MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + MP_STATE_VM(mp_kbd_exception).args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj; + #endif + + #if MICROPY_ENABLE_COMPILER + // optimization disabled by default + MP_STATE_VM(mp_optimise_value) = 0; + #if MICROPY_EMIT_NATIVE + MP_STATE_VM(default_emit_opt) = MP_EMIT_OPT_NONE; + #endif + #endif + + // init global module dict + mp_obj_dict_init(&MP_STATE_VM(mp_loaded_modules_dict), 3); + if (MP_STATE_THREAD(active_exception) != NULL) { + return; + } + + // initialise the __main__ module + mp_obj_dict_init(&MP_STATE_VM(dict_main), 1); + if (MP_STATE_THREAD(active_exception) != NULL) { + return; + } + mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(dict_main)), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + + // locals = globals for outer module (see Objects/frameobject.c/PyFrame_New()) + mp_locals_set(&MP_STATE_VM(dict_main)); + mp_globals_set(&MP_STATE_VM(dict_main)); + + #if MICROPY_CAN_OVERRIDE_BUILTINS + // start with no extensions to builtins + MP_STATE_VM(mp_module_builtins_override_dict) = NULL; + #endif + + #if MICROPY_PERSISTENT_CODE_TRACK_RELOC_CODE + MP_STATE_VM(track_reloc_code_list) = MP_OBJ_NULL; + #endif + + #if MICROPY_PY_OS_DUPTERM + for (size_t i = 0; i < MICROPY_PY_OS_DUPTERM; ++i) { + MP_STATE_VM(dupterm_objs[i]) = MP_OBJ_NULL; + } + #endif + + #if MICROPY_VFS + // initialise the VFS sub-system + MP_STATE_VM(vfs_cur) = NULL; + MP_STATE_VM(vfs_mount_table) = NULL; + #endif + + #if MICROPY_PY_SYS_ATEXIT + MP_STATE_VM(sys_exitfunc) = mp_const_none; + #endif + + #if MICROPY_PY_SYS_SETTRACE + MP_STATE_THREAD(prof_trace_callback) = MP_OBJ_NULL; + MP_STATE_THREAD(prof_callback_is_executing) = false; + MP_STATE_THREAD(current_code_state) = NULL; + #endif + + #if MICROPY_PY_BLUETOOTH + MP_STATE_VM(bluetooth) = MP_OBJ_NULL; + #endif + + #if MICROPY_PY_THREAD_GIL + mp_thread_mutex_init(&MP_STATE_VM(gil_mutex)); + #endif + + // call port specific initialization if any + #ifdef MICROPY_PORT_INIT_FUNC + MICROPY_PORT_INIT_FUNC; + #endif + + MP_THREAD_GIL_ENTER(); +} + +void mp_deinit(void) { + MP_THREAD_GIL_EXIT(); + + // call port specific deinitialization if any + #ifdef MICROPY_PORT_DEINIT_FUNC + MICROPY_PORT_DEINIT_FUNC; + #endif + + //mp_obj_dict_free(&dict_main); + //mp_map_deinit(&MP_STATE_VM(mp_loaded_modules_map)); +} + +mp_obj_t mp_load_name(qstr qst) { + // logic: search locals, globals, builtins + DEBUG_OP_printf("load name %s\n", qstr_str(qst)); + // If we're at the outer scope (locals == globals), dispatch to load_global right away + if (mp_locals_get() != mp_globals_get()) { + mp_map_elem_t *elem = mp_map_lookup(&mp_locals_get()->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem != NULL) { + return elem->value; + } + } + return mp_load_global(qst); +} + +mp_obj_t mp_load_global(qstr qst) { + // logic: search globals, builtins + DEBUG_OP_printf("load global %s\n", qstr_str(qst)); + mp_map_elem_t *elem = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem == NULL) { + #if MICROPY_CAN_OVERRIDE_BUILTINS + if (MP_STATE_VM(mp_module_builtins_override_dict) != NULL) { + // lookup in additional dynamic table of builtins first + elem = mp_map_lookup(&MP_STATE_VM(mp_module_builtins_override_dict)->map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem != NULL) { + return elem->value; + } + } + #endif + elem = mp_map_lookup((mp_map_t *)&mp_module_builtins_globals.map, MP_OBJ_NEW_QSTR(qst), MP_MAP_LOOKUP); + if (elem == NULL) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_msg(&mp_type_NameError, MP_ERROR_TEXT("name not defined")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_NameError, + MP_ERROR_TEXT("name '%q' isn't defined"), qst)); + #endif + } + } + return elem->value; +} + +mp_obj_t mp_load_build_class(void) { + DEBUG_OP_printf("load_build_class\n"); + #if MICROPY_CAN_OVERRIDE_BUILTINS + if (MP_STATE_VM(mp_module_builtins_override_dict) != NULL) { + // lookup in additional dynamic table of builtins first + mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_module_builtins_override_dict)->map, MP_OBJ_NEW_QSTR(MP_QSTR___build_class__), MP_MAP_LOOKUP); + if (elem != NULL) { + return elem->value; + } + } + #endif + return MP_OBJ_FROM_PTR(&mp_builtin___build_class___obj); +} + +void mp_store_name(qstr qst, mp_obj_t obj) { + DEBUG_OP_printf("store name %s <- %p\n", qstr_str(qst), obj); + mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst), obj); +} + +mp_obj_t mp_delete_name(qstr qst) { + DEBUG_OP_printf("delete name %s\n", qstr_str(qst)); + // TODO convert KeyError to NameError if qst not found + return mp_obj_dict_delete(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst)); +} + +void mp_store_global(qstr qst, mp_obj_t obj) { + DEBUG_OP_printf("store global %s <- %p\n", qstr_str(qst), obj); + mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(qst), obj); +} + +mp_obj_t mp_delete_global(qstr qst) { + DEBUG_OP_printf("delete global %s\n", qstr_str(qst)); + // TODO convert KeyError to NameError if qst not found + return mp_obj_dict_delete(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(qst)); +} + +mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg) { + DEBUG_OP_printf("unary " UINT_FMT " %q %p\n", op, mp_unary_op_method_name[op], arg); + + if (op == MP_UNARY_OP_NOT) { + // "not x" is the negative of whether "x" is true per Python semantics + return mp_obj_new_bool(mp_obj_is_true(arg) == 0); + } else if (mp_obj_is_small_int(arg)) { + mp_int_t val = MP_OBJ_SMALL_INT_VALUE(arg); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(val != 0); + case MP_UNARY_OP_HASH: + return arg; + case MP_UNARY_OP_POSITIVE: + case MP_UNARY_OP_INT: + return arg; + case MP_UNARY_OP_NEGATIVE: + // check for overflow + if (val == MP_SMALL_INT_MIN) { + return mp_obj_new_int(-val); + } else { + return MP_OBJ_NEW_SMALL_INT(-val); + } + case MP_UNARY_OP_ABS: + if (val >= 0) { + return arg; + } else if (val == MP_SMALL_INT_MIN) { + // check for overflow + return mp_obj_new_int(-val); + } else { + return MP_OBJ_NEW_SMALL_INT(-val); + } + default: + assert(op == MP_UNARY_OP_INVERT); + return MP_OBJ_NEW_SMALL_INT(~val); + } + } else if (op == MP_UNARY_OP_HASH && mp_obj_is_str_or_bytes(arg)) { + // fast path for hashing str/bytes + GET_STR_HASH(arg, h); + if (h == 0) { + GET_STR_DATA_LEN(arg, data, len); + h = qstr_compute_hash(data, len); + } + return MP_OBJ_NEW_SMALL_INT(h); + } else { + const mp_obj_type_t *type = mp_obj_get_type(arg); + if (type->unary_op != NULL) { + mp_obj_t result = type->unary_op(op, arg); + if (result != MP_OBJ_NULL) { + return result; + } + } + // With MP_UNARY_OP_INT, mp_unary_op() becomes a fallback for mp_obj_get_int(). + // In this case provide a more focused error message to not confuse, e.g. chr(1.0) + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + if (op == MP_UNARY_OP_INT) { + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); + } else { + mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); + } + } else { + if (op == MP_UNARY_OP_INT) { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("can't convert %s to int"), mp_obj_get_type_str(arg))); + } else { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("unsupported type for %q: '%s'"), + mp_unary_op_method_name[op], mp_obj_get_type_str(arg))); + } + } + } +} + +mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { + DEBUG_OP_printf("binary " UINT_FMT " %q %p %p\n", op, mp_binary_op_method_name[op], lhs, rhs); + + // TODO correctly distinguish inplace operators for mutable objects + // lookup logic that CPython uses for +=: + // check for implemented += + // then check for implemented + + // then check for implemented seq.inplace_concat + // then check for implemented seq.concat + // then fail + // note that list does not implement + or +=, so that inplace_concat is reached first for += + + // deal with is + if (op == MP_BINARY_OP_IS) { + return mp_obj_new_bool(lhs == rhs); + } + + // deal with == and != for all types + if (op == MP_BINARY_OP_EQUAL || op == MP_BINARY_OP_NOT_EQUAL) { + // mp_obj_equal_not_equal supports a bunch of shortcuts + return mp_obj_equal_not_equal(op, lhs, rhs); + } + + // deal with exception_match for all types + if (op == MP_BINARY_OP_EXCEPTION_MATCH) { + // rhs must be issubclass(rhs, BaseException) + if (mp_obj_is_exception_type(rhs)) { + if (mp_obj_exception_match(lhs, rhs)) { + return mp_const_true; + } else { + return mp_const_false; + } + } else if (mp_obj_is_type(rhs, &mp_type_tuple)) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(rhs); + for (size_t i = 0; i < tuple->len; i++) { + rhs = tuple->items[i]; + if (!mp_obj_is_exception_type(rhs)) { + goto unsupported_op; + } + if (mp_obj_exception_match(lhs, rhs)) { + return mp_const_true; + } + } + return mp_const_false; + } + goto unsupported_op; + } + + if (mp_obj_is_small_int(lhs)) { + mp_int_t lhs_val = MP_OBJ_SMALL_INT_VALUE(lhs); + if (mp_obj_is_small_int(rhs)) { + mp_int_t rhs_val = MP_OBJ_SMALL_INT_VALUE(rhs); + // This is a binary operation: lhs_val op rhs_val + // We need to be careful to handle overflow; see CERT INT32-C + // Operations that can overflow: + // + result always fits in mp_int_t, then handled by SMALL_INT check + // - result always fits in mp_int_t, then handled by SMALL_INT check + // * checked explicitly + // / if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check + // % if lhs=MIN and rhs=-1; result always fits in mp_int_t, then handled by SMALL_INT check + // << checked explicitly + switch (op) { + case MP_BINARY_OP_OR: + case MP_BINARY_OP_INPLACE_OR: + lhs_val |= rhs_val; + break; + case MP_BINARY_OP_XOR: + case MP_BINARY_OP_INPLACE_XOR: + lhs_val ^= rhs_val; + break; + case MP_BINARY_OP_AND: + case MP_BINARY_OP_INPLACE_AND: + lhs_val &= rhs_val; + break; + case MP_BINARY_OP_LSHIFT: + case MP_BINARY_OP_INPLACE_LSHIFT: { + if (rhs_val < 0) { + // negative shift not allowed + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); + } else if (rhs_val >= (mp_int_t)(sizeof(lhs_val) * MP_BITS_PER_BYTE) + || lhs_val > (MP_SMALL_INT_MAX >> rhs_val) + || lhs_val < (MP_SMALL_INT_MIN >> rhs_val)) { + // left-shift will overflow, so use higher precision integer + lhs = mp_obj_new_int_from_ll(lhs_val); + goto generic_binary_op; + } else { + // use standard precision + lhs_val <<= rhs_val; + } + break; + } + case MP_BINARY_OP_RSHIFT: + case MP_BINARY_OP_INPLACE_RSHIFT: + if (rhs_val < 0) { + // negative shift not allowed + mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); + } else { + // standard precision is enough for right-shift + if (rhs_val >= (mp_int_t)(sizeof(lhs_val) * MP_BITS_PER_BYTE)) { + // Shifting to big amounts is underfined behavior + // in C and is CPU-dependent; propagate sign bit. + rhs_val = sizeof(lhs_val) * MP_BITS_PER_BYTE - 1; + } + lhs_val >>= rhs_val; + } + break; + case MP_BINARY_OP_ADD: + case MP_BINARY_OP_INPLACE_ADD: + lhs_val += rhs_val; + break; + case MP_BINARY_OP_SUBTRACT: + case MP_BINARY_OP_INPLACE_SUBTRACT: + lhs_val -= rhs_val; + break; + case MP_BINARY_OP_MULTIPLY: + case MP_BINARY_OP_INPLACE_MULTIPLY: { + + // If long long type exists and is larger than mp_int_t, then + // we can use the following code to perform overflow-checked multiplication. + // Otherwise (eg in x64 case) we must use mp_small_int_mul_overflow. + #if 0 + // compute result using long long precision + long long res = (long long)lhs_val * (long long)rhs_val; + if (res > MP_SMALL_INT_MAX || res < MP_SMALL_INT_MIN) { + // result overflowed SMALL_INT, so return higher precision integer + return mp_obj_new_int_from_ll(res); + } else { + // use standard precision + lhs_val = (mp_int_t)res; + } + #endif + + if (mp_small_int_mul_overflow(lhs_val, rhs_val)) { + // use higher precision + lhs = mp_obj_new_int_from_ll(lhs_val); + goto generic_binary_op; + } else { + // use standard precision + return MP_OBJ_NEW_SMALL_INT(lhs_val * rhs_val); + } + } + case MP_BINARY_OP_FLOOR_DIVIDE: + case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE: + if (rhs_val == 0) { + goto zero_division; + } + lhs_val = mp_small_int_floor_divide(lhs_val, rhs_val); + break; + + #if MICROPY_PY_BUILTINS_FLOAT + case MP_BINARY_OP_TRUE_DIVIDE: + case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + if (rhs_val == 0) { + goto zero_division; + } + return mp_obj_new_float((mp_float_t)lhs_val / (mp_float_t)rhs_val); + #endif + + case MP_BINARY_OP_MODULO: + case MP_BINARY_OP_INPLACE_MODULO: { + if (rhs_val == 0) { + goto zero_division; + } + lhs_val = mp_small_int_modulo(lhs_val, rhs_val); + break; + } + + case MP_BINARY_OP_POWER: + case MP_BINARY_OP_INPLACE_POWER: + if (rhs_val < 0) { + #if MICROPY_PY_BUILTINS_FLOAT + return mp_obj_float_binary_op(op, lhs_val, rhs); + #else + mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support")); + #endif + } else { + mp_int_t ans = 1; + while (rhs_val > 0) { + if (rhs_val & 1) { + if (mp_small_int_mul_overflow(ans, lhs_val)) { + goto power_overflow; + } + ans *= lhs_val; + } + if (rhs_val == 1) { + break; + } + rhs_val /= 2; + if (mp_small_int_mul_overflow(lhs_val, lhs_val)) { + goto power_overflow; + } + lhs_val *= lhs_val; + } + lhs_val = ans; + } + break; + + power_overflow: + // use higher precision + lhs = mp_obj_new_int_from_ll(MP_OBJ_SMALL_INT_VALUE(lhs)); + goto generic_binary_op; + + case MP_BINARY_OP_DIVMOD: { + if (rhs_val == 0) { + goto zero_division; + } + // to reduce stack usage we don't pass a temp array of the 2 items + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(mp_small_int_floor_divide(lhs_val, rhs_val)); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(mp_small_int_modulo(lhs_val, rhs_val)); + return MP_OBJ_FROM_PTR(tuple); + } + + case MP_BINARY_OP_LESS: + return mp_obj_new_bool(lhs_val < rhs_val); + case MP_BINARY_OP_MORE: + return mp_obj_new_bool(lhs_val > rhs_val); + case MP_BINARY_OP_LESS_EQUAL: + return mp_obj_new_bool(lhs_val <= rhs_val); + case MP_BINARY_OP_MORE_EQUAL: + return mp_obj_new_bool(lhs_val >= rhs_val); + + default: + goto unsupported_op; + } + // This is an inlined version of mp_obj_new_int, for speed + if (MP_SMALL_INT_FITS(lhs_val)) { + return MP_OBJ_NEW_SMALL_INT(lhs_val); + } else { + return mp_obj_new_int_from_ll(lhs_val); + } + #if MICROPY_PY_BUILTINS_FLOAT + } else if (mp_obj_is_float(rhs)) { + mp_obj_t res = mp_obj_float_binary_op(op, lhs_val, rhs); + if (res == MP_OBJ_NULL) { + goto unsupported_op; + } else { + return res; + } + #endif + #if MICROPY_PY_BUILTINS_COMPLEX + } else if (mp_obj_is_type(rhs, &mp_type_complex)) { + mp_obj_t res = mp_obj_complex_binary_op(op, lhs_val, 0, rhs); + if (res == MP_OBJ_NULL) { + goto unsupported_op; + } else { + return res; + } + #endif + } + } + + // Convert MP_BINARY_OP_IN to MP_BINARY_OP_CONTAINS with swapped args. + if (op == MP_BINARY_OP_IN) { + op = MP_BINARY_OP_CONTAINS; + mp_obj_t temp = lhs; + lhs = rhs; + rhs = temp; + } + + // generic binary_op supplied by type + const mp_obj_type_t *type; +generic_binary_op: + type = mp_obj_get_type(lhs); + if (type->binary_op != NULL) { + mp_obj_t result = type->binary_op(op, lhs, rhs); + if (result != MP_OBJ_NULL) { + return result; + } + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + } + + #if MICROPY_PY_REVERSE_SPECIAL_METHODS + if (op >= MP_BINARY_OP_OR && op <= MP_BINARY_OP_POWER) { + mp_obj_t t = rhs; + rhs = lhs; + lhs = t; + op += MP_BINARY_OP_REVERSE_OR - MP_BINARY_OP_OR; + goto generic_binary_op; + } else if (op >= MP_BINARY_OP_REVERSE_OR) { + // Convert __rop__ back to __op__ for error message + mp_obj_t t = rhs; + rhs = lhs; + lhs = t; + op -= MP_BINARY_OP_REVERSE_OR - MP_BINARY_OP_OR; + } + #endif + + if (op == MP_BINARY_OP_CONTAINS) { + // If type didn't support containment then explicitly walk the iterator. + // mp_getiter will raise the appropriate exception if lhs is not iterable. + mp_obj_iter_buf_t iter_buf; + mp_obj_t iter = mp_getiter(lhs, &iter_buf); + if (iter == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } + mp_obj_t next; + while ((next = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + if (mp_obj_equal(next, rhs)) { + return mp_const_true; + } + } + return mp_const_false; + } + +unsupported_op: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("unsupported type for operator")); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("unsupported types for %q: '%s', '%s'"), + mp_binary_op_method_name[op], mp_obj_get_type_str(lhs), mp_obj_get_type_str(rhs))); + #endif + +zero_division: + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); +} + +mp_obj_t mp_call_function_0(mp_obj_t fun) { + return mp_call_function_n_kw(fun, 0, 0, NULL); +} + +mp_obj_t mp_call_function_1(mp_obj_t fun, mp_obj_t arg) { + return mp_call_function_n_kw(fun, 1, 0, &arg); +} + +mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2) { + mp_obj_t args[2]; + args[0] = arg1; + args[1] = arg2; + return mp_call_function_n_kw(fun, 2, 0, args); +} + +// args contains, eg: arg0 arg1 key0 value0 key1 value1 +mp_obj_t mp_call_function_n_kw(mp_obj_t fun_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // TODO improve this: fun object can specify its type and we parse here the arguments, + // passing to the function arrays of fixed and keyword arguments + + DEBUG_OP_printf("calling function %p(n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", fun_in, n_args, n_kw, args); + + if (!fun_in) { +#if defined(__WASI__) || defined(__EMSCRIPTEN__) + cdbg("660:calling function %p(n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", fun_in, (unsigned int)n_args, (unsigned int)n_kw, args); +#else +// x64 + cdbg("660:calling function %p(n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", fun_in, (long unsigned int)n_args, (long unsigned int)n_kw, args); +#endif + } else { + // get the type + const mp_obj_type_t *type = mp_obj_get_type(fun_in); + + // do the call + if (type->call != NULL) { + return type->call(fun_in, n_args, n_kw, args); + } + } + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + return mp_raise_TypeError_o("object not callable"); + #else + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "'%s' object isn't callable", mp_obj_get_type_str(fun_in))); + #endif +} + +// args contains: fun self/NULL arg(0) ... arg(n_args-2) arg(n_args-1) kw_key(0) kw_val(0) ... kw_key(n_kw-1) kw_val(n_kw-1) +// if n_args==0 and n_kw==0 then there are only fun and self/NULL +mp_obj_t mp_call_method_n_kw(size_t n_args, size_t n_kw, const mp_obj_t *args) { + DEBUG_OP_printf("call method (fun=%p, self=%p, n_args=" UINT_FMT ", n_kw=" UINT_FMT ", args=%p)\n", args[0], args[1], n_args, n_kw, args); + int adjust = (args[1] == MP_OBJ_NULL) ? 0 : 1; + return mp_call_function_n_kw(args[0], n_args + adjust, n_kw, args + 2 - adjust); +} + +// This function only needs to be exposed externally when in stackless mode. +#if !MICROPY_STACKLESS +STATIC +#endif +int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args) { + mp_obj_t fun = *args++; + mp_obj_t self = MP_OBJ_NULL; + if (have_self) { + self = *args++; // may be MP_OBJ_NULL + } + uint n_args = n_args_n_kw & 0xff; + uint n_kw = (n_args_n_kw >> 8) & 0xff; + mp_obj_t pos_seq = args[n_args + 2 * n_kw]; // may be MP_OBJ_NULL + mp_obj_t kw_dict = args[n_args + 2 * n_kw + 1]; // may be MP_OBJ_NULL + + DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, seq=%p, dict=%p)\n", fun, self, n_args, n_kw, args, pos_seq, kw_dict); + + // We need to create the following array of objects: + // args[0 .. n_args] unpacked(pos_seq) args[n_args .. n_args + 2 * n_kw] unpacked(kw_dict) + // TODO: optimize one day to avoid constructing new arg array? Will be hard. + + // The new args array + mp_obj_t *args2; + uint args2_alloc; + uint args2_len = 0; + + // Try to get a hint for the size of the kw_dict + uint kw_dict_len = 0; + if (kw_dict != MP_OBJ_NULL && mp_obj_is_type(kw_dict, &mp_type_dict)) { + kw_dict_len = mp_obj_dict_len(kw_dict); + } + + // Extract the pos_seq sequence to the new args array. + // Note that it can be arbitrary iterator. + if (pos_seq == MP_OBJ_NULL) { + // no sequence + + // allocate memory for the new array of args + args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len); + args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); + + // copy the self + if (self != MP_OBJ_NULL) { + args2[args2_len++] = self; + } + + // copy the fixed pos args + mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); + args2_len += n_args; + + } else if (mp_obj_is_type(pos_seq, &mp_type_tuple) || mp_obj_is_type(pos_seq, &mp_type_list)) { + // optimise the case of a tuple and list + + // get the items + size_t len; + mp_obj_t *items; + mp_obj_get_array(pos_seq, &len, &items); + + // allocate memory for the new array of args + args2_alloc = 1 + n_args + len + 2 * (n_kw + kw_dict_len); + args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); + + // copy the self + if (self != MP_OBJ_NULL) { + args2[args2_len++] = self; + } + + // copy the fixed and variable position args + mp_seq_cat(args2 + args2_len, args, n_args, items, len, mp_obj_t); + args2_len += n_args + len; + + } else { + // generic iterator + + // allocate memory for the new array of args + args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len) + 3; + args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t)); + + // copy the self + if (self != MP_OBJ_NULL) { + args2[args2_len++] = self; + } + + // copy the fixed position args + mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t); + args2_len += n_args; + + // extract the variable position args from the iterator + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(pos_seq, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (args2_len >= args2_alloc) { + args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), args2_alloc * 2 * sizeof(mp_obj_t)); + args2_alloc *= 2; + } + args2[args2_len++] = item; + } + } + + // The size of the args2 array now is the number of positional args. + uint pos_args_len = args2_len; + + // Copy the fixed kw args. + mp_seq_copy(args2 + args2_len, args + n_args, 2 * n_kw, mp_obj_t); + args2_len += 2 * n_kw; + + // Extract (key,value) pairs from kw_dict dictionary and append to args2. + // Note that it can be arbitrary iterator. + if (kw_dict == MP_OBJ_NULL) { + // pass + } else if (mp_obj_is_type(kw_dict, &mp_type_dict)) { + // dictionary + mp_map_t *map = mp_obj_dict_get_map(kw_dict); + assert(args2_len + 2 * map->used <= args2_alloc); // should have enough, since kw_dict_len is in this case hinted correctly above + for (size_t i = 0; i < map->alloc; i++) { + if (mp_map_slot_is_filled(map, i)) { + // the key must be a qstr, so intern it if it's a string + mp_obj_t key = map->table[i].key; + if (!mp_obj_is_qstr(key)) { + key = mp_obj_str_intern_checked(key); + if (key == MP_OBJ_NULL) { + // exception + return 1; + } + } + args2[args2_len++] = key; + args2[args2_len++] = map->table[i].value; + } + } + } else { + // generic mapping: + // - call keys() to get an iterable of all keys in the mapping + // - call __getitem__ for each key to get the corresponding value + + // get the keys iterable + mp_obj_t dest[3]; + mp_load_method(kw_dict, MP_QSTR_keys, dest); + mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest), NULL); + + mp_obj_t key; + while ((key = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + // expand size of args array if needed + if (args2_len + 1 >= args2_alloc) { + uint new_alloc = args2_alloc * 2; + if (new_alloc < 4) { + new_alloc = 4; + } + args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), new_alloc * sizeof(mp_obj_t)); + args2_alloc = new_alloc; + } + + // the key must be a qstr, so intern it if it's a string + if (!mp_obj_is_qstr(key)) { + key = mp_obj_str_intern_checked(key); + } + + // get the value corresponding to the key + mp_load_method(kw_dict, MP_QSTR___getitem__, dest); + dest[2] = key; + mp_obj_t value = mp_call_method_n_kw(1, 0, dest); + + // store the key/value pair in the argument array + args2[args2_len++] = key; + args2[args2_len++] = value; + } + } + + out_args->fun = fun; + out_args->args = args2; + out_args->n_args = pos_args_len; + out_args->n_kw = (args2_len - pos_args_len) / 2; + out_args->n_alloc = args2_alloc; + + return 0; +} + +mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args) { + mp_call_args_t out_args; + if (mp_call_prepare_args_n_kw_var(have_self, n_args_n_kw, args, &out_args)) { + return MP_OBJ_NULL; + } + + mp_obj_t res = mp_call_function_n_kw(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); + mp_nonlocal_free(out_args.args, out_args.n_alloc * sizeof(mp_obj_t)); + + return res; +} + +// unpacked items are stored in reverse order into the array pointed to by items +mp_obj_t mp_unpack_sequence(mp_obj_t seq_in, size_t num, mp_obj_t *items) { + size_t seq_len; + if (mp_obj_is_type(seq_in, &mp_type_tuple) || mp_obj_is_type(seq_in, &mp_type_list)) { + mp_obj_t *seq_items; + mp_obj_get_array(seq_in, &seq_len, &seq_items); + if (seq_len < num) { + goto too_short; + } else if (seq_len > num) { + goto too_long; + } + for (size_t i = 0; i < num; i++) { + items[i] = seq_items[num - 1 - i]; + } + } else { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(seq_in, &iter_buf); + + for (seq_len = 0; seq_len < num; seq_len++) { + mp_obj_t el = mp_iternext(iterable); + if (el == MP_OBJ_STOP_ITERATION) { + goto too_short; + } + items[num - 1 - seq_len] = el; + } + if (mp_iternext(iterable) != MP_OBJ_STOP_ITERATION) { + goto too_long; + } + } + return MP_OBJ_SENTINEL; // success + +too_short: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len)); + #endif +too_long: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_ValueError_o(MP_ERROR_TEXT("wrong number of values to unpack")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("too many values to unpack (expected %d)"), (int)num)); + #endif +} + +// unpacked items are stored in reverse order into the array pointed to by items +mp_obj_t mp_unpack_ex(mp_obj_t seq_in, size_t num_in, mp_obj_t *items) { + size_t num_left = num_in & 0xff; + size_t num_right = (num_in >> 8) & 0xff; + DEBUG_OP_printf("unpack ex " UINT_FMT " " UINT_FMT "\n", num_left, num_right); + size_t seq_len; + if (mp_obj_is_type(seq_in, &mp_type_tuple) || mp_obj_is_type(seq_in, &mp_type_list)) { + // Make the seq variable volatile so the compiler keeps a reference to it, + // since if it's a tuple then seq_items points to the interior of the GC cell + // and mp_obj_new_list may trigger a GC which doesn't trace this and reclaims seq. + volatile mp_obj_t seq = seq_in; + mp_obj_t *seq_items; + mp_obj_get_array(seq, &seq_len, &seq_items); + if (seq_len < num_left + num_right) { + goto too_short; + } + for (size_t i = 0; i < num_right; i++) { + items[i] = seq_items[seq_len - 1 - i]; + } + items[num_right] = mp_obj_new_list(seq_len - num_left - num_right, seq_items + num_left); + for (size_t i = 0; i < num_left; i++) { + items[num_right + 1 + i] = seq_items[num_left - 1 - i]; + } + seq = MP_OBJ_NULL; + } else { + // Generic iterable; this gets a bit messy: we unpack known left length to the + // items destination array, then the rest to a dynamically created list. Once the + // iterable is exhausted, we take from this list for the right part of the items. + // TODO Improve to waste less memory in the dynamically created list. + mp_obj_t iterable = mp_getiter(seq_in, NULL); + mp_obj_t item; + for (seq_len = 0; seq_len < num_left; seq_len++) { + item = mp_iternext(iterable); + if (item == MP_OBJ_STOP_ITERATION) { + goto too_short; + } + items[num_left + num_right + 1 - 1 - seq_len] = item; + } + mp_obj_list_t *rest = MP_OBJ_TO_PTR(mp_obj_new_list(0, NULL)); + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + mp_obj_list_append(MP_OBJ_FROM_PTR(rest), item); + } + if (rest->len < num_right) { + goto too_short; + } + items[num_right] = MP_OBJ_FROM_PTR(rest); + for (size_t i = 0; i < num_right; i++) { + items[num_right - 1 - i] = rest->items[rest->len - num_right + i]; + } + mp_obj_list_set_len(MP_OBJ_FROM_PTR(rest), rest->len - num_right); + } + return MP_OBJ_SENTINEL; // success + +too_short: + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_ValueError(MP_ERROR_TEXT("wrong number of values to unpack")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("need more than %d values to unpack"), (int)seq_len)); + #endif + return MP_OBJ_NULL; +} + +mp_obj_t mp_load_attr(mp_obj_t base, qstr attr) { + DEBUG_OP_printf("load attr %p.%s\n", base, qstr_str(attr)); + // use load_method + mp_obj_t dest[2]; + if (mp_load_method(base, attr, dest) == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + if (dest[1] == MP_OBJ_NULL) { + // load_method returned just a normal attribute + return dest[0]; + } else { + // load_method returned a method, so build a bound method object + return mp_obj_new_bound_meth(dest[0], dest[1]); + } +} + +#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + +// The following "checked fun" type is local to the mp_convert_member_lookup +// function, and serves to check that the first argument to a builtin function +// has the correct type. + +typedef struct _mp_obj_checked_fun_t { + mp_obj_base_t base; + const mp_obj_type_t *type; + mp_obj_t fun; +} mp_obj_checked_fun_t; + +STATIC mp_obj_t checked_fun_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_obj_checked_fun_t *self = MP_OBJ_TO_PTR(self_in); + if (n_args > 0) { + const mp_obj_type_t *arg0_type = mp_obj_get_type(args[0]); + if (arg0_type != self->type) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("argument has wrong type")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("argument should be a '%q' not a '%q'"), self->type->name, arg0_type->name)); + #endif + } + } + return mp_call_function_n_kw(self->fun, n_args, n_kw, args); +} + +STATIC const mp_obj_type_t mp_type_checked_fun = { + { &mp_type_type }, + .name = MP_QSTR_function, + .call = checked_fun_call, +}; + +STATIC mp_obj_t mp_obj_new_checked_fun(const mp_obj_type_t *type, mp_obj_t fun) { + mp_obj_checked_fun_t *o = m_new_obj(mp_obj_checked_fun_t); + o->base.type = &mp_type_checked_fun; + o->type = type; + o->fun = fun; + return MP_OBJ_FROM_PTR(o); +} + +#endif // MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + +// Given a member that was extracted from an instance, convert it correctly +// and put the result in the dest[] array for a possible method call. +// Conversion means dealing with static/class methods, callables, and values. +// see http://docs.python.org/3/howto/descriptor.html +void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t member, mp_obj_t *dest) { + if (mp_obj_is_type(member, &mp_type_staticmethod)) { + // return just the function + dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; + } else if (mp_obj_is_type(member, &mp_type_classmethod)) { + // return a bound method, with self being the type of this object + // this type should be the type of the original instance, not the base + // type (which is what is passed in the 'type' argument to this function) + if (self != MP_OBJ_NULL) { + type = mp_obj_get_type(self); + } + dest[0] = ((mp_obj_static_class_method_t *)MP_OBJ_TO_PTR(member))->fun; + dest[1] = MP_OBJ_FROM_PTR(type); + } else if (mp_obj_is_type(member, &mp_type_type)) { + // Don't try to bind types (even though they're callable) + dest[0] = member; + } else if (mp_obj_is_fun(member) + || (mp_obj_is_obj(member) + && (((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_closure + || ((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_generator))) { + // only functions, closures and generators objects can be bound to self + #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + const mp_obj_type_t *m_type = ((mp_obj_base_t *)MP_OBJ_TO_PTR(member))->type; + if (self == MP_OBJ_NULL + && (m_type == &mp_type_fun_builtin_0 + || m_type == &mp_type_fun_builtin_1 + || m_type == &mp_type_fun_builtin_2 + || m_type == &mp_type_fun_builtin_3 + || m_type == &mp_type_fun_builtin_var) + && type != &mp_type_object) { + // we extracted a builtin method without a first argument, so we must + // wrap this function in a type checker + // Note that object will do its own checking so shouldn't be wrapped. + dest[0] = mp_obj_new_checked_fun(type, member); + } else + #endif + { + // return a bound method, with self being this object + dest[0] = member; + dest[1] = self; + } + } else { + // class member is a value, so just return that value + dest[0] = member; + } +} + +// no attribute found, returns: dest[0] == MP_OBJ_NULL, dest[1] == MP_OBJ_NULL +// normal attribute found, returns: dest[0] == , dest[1] == MP_OBJ_NULL +// method attribute found, returns: dest[0] == , dest[1] == +mp_obj_t mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { + // clear output to indicate no attribute/method found yet + dest[0] = MP_OBJ_NULL; + dest[1] = MP_OBJ_NULL; + + // get the type + const mp_obj_type_t *type = mp_obj_get_type(obj); + + // look for built-in names + #if MICROPY_CPYTHON_COMPAT + if (attr == MP_QSTR___class__) { + // a.__class__ is equivalent to type(a) + dest[0] = MP_OBJ_FROM_PTR(type); + return MP_OBJ_SENTINEL; // success + } + if (attr == MP_QSTR___base__) { // PMPP https://github.com/micropython/micropython/pull/4368 + const mp_obj_type_t *t = MP_OBJ_TO_PTR(obj); + if (mp_obj_is_instance_type(t)) { + dest[0] = MP_OBJ_FROM_PTR(t->parent); + return MP_OBJ_SENTINEL; // success + } + } + #endif + + if (attr == MP_QSTR___next__ && type->iternext != NULL) { + dest[0] = MP_OBJ_FROM_PTR(&mp_builtin_next_obj); + dest[1] = obj; + + } else if (type->attr != NULL) { + // this type can do its own load, so call it + type->attr(obj, attr, dest); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + } else if (type->locals_dict != NULL) { + // generic method lookup + // this is a lookup in the object (ie not class or type) + assert(type->locals_dict->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &type->locals_dict->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); + if (elem != NULL) { + mp_convert_member_lookup(obj, type, elem->value, dest); + } + } + #if MICROPY_PY_FUNCTION_ATTRS //PMPP + else if (attr == MP_QSTR___name__) { + if ( type->name == MP_QSTR_function) { + dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(obj)); + } + } + #endif + return MP_OBJ_SENTINEL; // success +} + +mp_obj_t mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { + DEBUG_OP_printf("load method %p.%s\n", base, qstr_str(attr)); + if (!base){ + cdbg("1146:load method %p.%s\n", base, qstr_str(attr)); + return MP_OBJ_NULL; + } + if (mp_load_method_maybe(base, attr, dest) == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } + + if (dest[0] == MP_OBJ_NULL) { + // no attribute/method called attr + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_msg_o(&mp_type_AttributeError, "no such attribute"); + } else { + // following CPython, we give a more detailed error message for type objects + if (mp_obj_is_type(base, &mp_type_type)) { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, + MP_ERROR_TEXT("type object '%q' has no attribute '%q'"), + ((mp_obj_type_t *)MP_OBJ_TO_PTR(base))->name, attr)); + } else { + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, + MP_ERROR_TEXT("'%s' object has no attribute '%q'"), + mp_obj_get_type_str(base), attr)); + } + } + } + + return MP_OBJ_SENTINEL; // success +} + +// Acts like mp_load_method_maybe but catches AttributeError, and all other exceptions if requested +mp_obj_t mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catch_all_exc) { + if (mp_load_method_maybe(obj, attr, dest) == MP_OBJ_NULL) { + // exception + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; + if (!catch_all_exc + && !mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), + MP_OBJ_FROM_PTR(&mp_type_AttributeError))) { + // Re-raise the exception + return mp_raise_o(MP_OBJ_FROM_PTR(exc)); + } + } + return MP_OBJ_SENTINEL; // success +} + +mp_obj_t mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) { + DEBUG_OP_printf("store attr %p.%s <- %p\n", base, qstr_str(attr), value); + const mp_obj_type_t *type = mp_obj_get_type(base); + if (type->attr != NULL) { + mp_obj_t dest[2] = {MP_OBJ_SENTINEL, value}; + type->attr(base, attr, dest); + if (MP_STATE_THREAD(active_exception) != NULL) { + // exception + return MP_OBJ_NULL; + } + if (dest[0] == MP_OBJ_NULL) { + // success + return MP_OBJ_SENTINEL; + } + } + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("no such attribute")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, + MP_ERROR_TEXT("'%s' object has no attribute '%q'"), + mp_obj_get_type_str(base), attr)); + #endif +} + +mp_obj_t mp_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + if (!o_in) mp_raise_TypeError(MP_ERROR_TEXT("1219:object not iterable")); + const mp_obj_type_t *type = mp_obj_get_type(o_in); + + // Check for native getiter which is the identity. We handle this case explicitly + // so we don't unnecessarily allocate any RAM for the iter_buf, which won't be used. + if (type->getiter == mp_identity_getiter) { + return o_in; + } + + // check for native getiter (corresponds to __iter__) + if (type->getiter != NULL) { + if (iter_buf == NULL && type->getiter != mp_obj_instance_getiter) { + // if caller did not provide a buffer then allocate one on the heap + // mp_obj_instance_getiter is special, it will allocate only if needed + iter_buf = m_new_obj(mp_obj_iter_buf_t); + } + mp_obj_t iter = type->getiter(o_in, iter_buf); + if (iter != MP_OBJ_NULL) { + return iter; + } + } + + // check for __getitem__ + mp_obj_t dest[2]; + mp_load_method_maybe(o_in, MP_QSTR___getitem__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __getitem__ exists, create and return an iterator + if (iter_buf == NULL) { + // if caller did not provide a buffer then allocate one on the heap + iter_buf = m_new_obj(mp_obj_iter_buf_t); + } + return mp_obj_new_getitem_iter(dest, iter_buf); + } + // object not iterable + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("object not iterable")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't iterable"), mp_obj_get_type_str(o_in))); + #endif + +} + +// may return MP_OBJ_STOP_ITERATION as an optimisation instead of raise StopIteration() +// may also raise StopIteration() +mp_obj_t mp_iternext_allow_raise(mp_obj_t o_in) { + if (!o_in) mp_raise_TypeError(MP_ERROR_TEXT("1265:NPE:object not an iterator")); + const mp_obj_type_t *type = mp_obj_get_type(o_in); + if (type->iternext != NULL) { + return type->iternext(o_in); + } else { + // check for __next__ method + mp_obj_t dest[2]; + mp_load_method_maybe(o_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __next__ exists, call it and return its result + return mp_call_method_n_kw(0, 0, dest); + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't an iterator"), mp_obj_get_type_str(o_in))); + #endif + } + } +} + +// will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration() (or any subclass thereof) +// may raise other exceptions +mp_obj_t mp_iternext(mp_obj_t o_in) { + if (!o_in) mp_raise_TypeError(MP_ERROR_TEXT("1290:NPE:object not an iterator")); + if (MP_STACK_CHECK()) { // enumerate, filter, map and zip can recursively call mp_iternext + return MP_OBJ_NULL; + } + const mp_obj_type_t *type = mp_obj_get_type(o_in); + if (type->iternext != NULL) { + return type->iternext(o_in); + } else { + // check for __next__ method + mp_obj_t dest[2]; + mp_load_method_maybe(o_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + // __next__ exists, call it and return its result + mp_obj_t ret = mp_call_method_n_kw(0, 0, dest); + if (ret != MP_OBJ_NULL) { + return ret; + } else { + // exception + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + MP_STATE_THREAD(active_exception) = NULL; + return MP_OBJ_STOP_ITERATION; + } else { + // reraise + return MP_OBJ_NULL; + } + } + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("object not an iterator")); + #else + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't an iterator"), mp_obj_get_type_str(o_in))); + #endif + } + } +} + +mp_obj_t mp_iternext2(mp_obj_t o) { + if (o == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + o = mp_iternext(o); + if (o == MP_OBJ_STOP_ITERATION) { + MP_STATE_THREAD(active_exception) = (void*)1; + return MP_OBJ_NULL; + } + return o; +} + +bool mp_iternext_had_exc(void) { + if (MP_STATE_THREAD(active_exception) == (void*)1) { + MP_STATE_THREAD(active_exception) = NULL; + return false; + } + return MP_STATE_THREAD(active_exception) != NULL; +} + +#pragma message "TODO: Unclear what to do with StopIteration exception here." +mp_vm_return_kind_t mp_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { + assert((send_value != MP_OBJ_NULL) ^ (throw_value != MP_OBJ_NULL)); + const mp_obj_type_t *type = mp_obj_get_type(self_in); + + if (type == &mp_type_gen_instance) { + return mp_obj_gen_resume(self_in, send_value, throw_value, ret_val); + } + + if (type->iternext != NULL && send_value == mp_const_none) { + mp_obj_t ret = type->iternext(self_in); + if (ret == MP_OBJ_NULL) { + // exception + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; + } + *ret_val = ret; + if (ret != MP_OBJ_STOP_ITERATION) { + return MP_VM_RETURN_YIELD; + } else { + // Emulate raise StopIteration() + // Special case, handled in vm.c + return MP_VM_RETURN_NORMAL; + } + } + + mp_obj_t dest[3]; // Reserve slot for send() arg + + // Python instance iterator protocol + if (send_value == mp_const_none) { + mp_load_method_maybe(self_in, MP_QSTR___next__, dest); + if (dest[0] != MP_OBJ_NULL) { + *ret_val = mp_call_method_n_kw(0, 0, dest); + if (*ret_val == MP_OBJ_NULL) { + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; + } + return MP_VM_RETURN_YIELD; + } + } + + // Either python instance generator protocol, or native object + // generator protocol. + if (send_value != MP_OBJ_NULL) { + mp_load_method(self_in, MP_QSTR_send, dest); + dest[2] = send_value; + *ret_val = mp_call_method_n_kw(1, 0, dest); + return MP_VM_RETURN_YIELD; + } + + assert(throw_value != MP_OBJ_NULL); + { + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(throw_value)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { + mp_load_method_maybe(self_in, MP_QSTR_close, dest); + if (dest[0] != MP_OBJ_NULL) { + // TODO: Exceptions raised in close() are not propagated, + // printed to sys.stderr + *ret_val = mp_call_method_n_kw(0, 0, dest); + // We assume one can't "yield" from close() + return MP_VM_RETURN_NORMAL; + } + } else { + mp_load_method_maybe(self_in, MP_QSTR_throw, dest); + if (dest[0] != MP_OBJ_NULL) { + dest[2] = throw_value; + *ret_val = mp_call_method_n_kw(1, 0, dest); + // If .throw() method returned, we assume it's value to yield + // - any exception would be thrown with nlr_raise(). + return MP_VM_RETURN_YIELD; + } + } + // If there's nowhere to throw exception into, then we assume that object + // is just incapable to handle it, so any exception thrown into it + // will be propagated up. This behavior is approved by test_pep380.py + // test_delegation_of_close_to_non_generator(), + // test_delegating_throw_to_non_generator() + if (mp_obj_exception_match(throw_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError + *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, "generator raised StopIteration"); + } else { + *ret_val = mp_make_raise_obj(throw_value); + } + return MP_VM_RETURN_EXCEPTION; + } +} + +mp_obj_t mp_make_raise_obj(mp_obj_t o) { + DEBUG_printf("raise %p\n", o); + if (mp_obj_is_exception_type(o)) { + // o is an exception type (it is derived from BaseException (or is BaseException)) + // create and return a new exception instance by calling o + // TODO could have an option to disable traceback, then builtin exceptions (eg TypeError) + // could have const instances in ROM which we return here instead + return mp_call_function_n_kw(o, 0, 0, NULL); + } else if (mp_obj_is_exception_instance(o)) { + // o is an instance of an exception, so use it as the exception + return o; + } else { + // o cannot be used as an exception, so return a type error (which will be raised by the caller) + return mp_obj_new_exception_msg(&mp_type_TypeError, "exceptions must derive from BaseException"); + } +} + +mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) { + DEBUG_printf("import name '%s' level=%d\n", qstr_str(name), MP_OBJ_SMALL_INT_VALUE(level)); + + // build args array + mp_obj_t args[5]; + args[0] = MP_OBJ_NEW_QSTR(name); + args[1] = mp_const_none; // TODO should be globals + args[2] = mp_const_none; // TODO should be locals + args[3] = fromlist; + args[4] = level; + + #if MICROPY_CAN_OVERRIDE_BUILTINS + // Lookup __import__ and call that if it exists + mp_obj_dict_t *bo_dict = MP_STATE_VM(mp_module_builtins_override_dict); + if (bo_dict != NULL) { + mp_map_elem_t *import = mp_map_lookup(&bo_dict->map, MP_OBJ_NEW_QSTR(MP_QSTR___import__), MP_MAP_LOOKUP); + if (import != NULL) { + return mp_call_function_n_kw(import->value, 5, 0, args); + } + } + #endif + + return mp_builtin___import__(5, args); +} + +mp_obj_t mp_import_from(mp_obj_t module, qstr name) { + DEBUG_printf("import from %p %s\n", module, qstr_str(name)); + + mp_obj_t dest[2]; + + mp_load_method_maybe(module, name, dest); + + if (dest[1] != MP_OBJ_NULL) { + // Hopefully we can't import bound method from an object +import_error: + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("cannot import name %q"), name)); + } + + if (dest[0] != MP_OBJ_NULL) { + return dest[0]; + } + + #if MICROPY_ENABLE_EXTERNAL_IMPORT + + // See if it's a package, then can try FS import + if (!mp_obj_is_package(module)) { + goto import_error; + } + + mp_load_method_maybe(module, MP_QSTR___name__, dest); + size_t pkg_name_len; + const char *pkg_name = mp_obj_str_get_data(dest[0], &pkg_name_len); + + const uint dot_name_len = pkg_name_len + 1 + qstr_len(name); + char *dot_name = mp_local_alloc(dot_name_len); + memcpy(dot_name, pkg_name, pkg_name_len); + dot_name[pkg_name_len] = '.'; + memcpy(dot_name + pkg_name_len + 1, qstr_str(name), qstr_len(name)); + qstr dot_name_q = qstr_from_strn(dot_name, dot_name_len); + mp_local_free(dot_name); + + // For fromlist, pass sentinel "non empty" value to force returning of leaf module + return mp_import_name(dot_name_q, mp_const_true, MP_OBJ_NEW_SMALL_INT(0)); + + #else + + // Package import not supported with external imports disabled + goto import_error; + + #endif +} + +void mp_import_all(mp_obj_t module) { + DEBUG_printf("import all %p\n", module); + + // TODO: Support __all__ + mp_map_t *map = &mp_obj_module_get_globals(module)->map; + for (size_t i = 0; i < map->alloc; i++) { + if (mp_map_slot_is_filled(map, i)) { + // Entry in module global scope may be generated programmatically + // (and thus be not a qstr for longer names). Avoid turning it in + // qstr if it has '_' and was used exactly to save memory. + const char *name = mp_obj_str_get_str(map->table[i].key); + if (*name != '_') { + qstr qname = mp_obj_str_get_qstr(map->table[i].key); + mp_store_name(qname, map->table[i].value); + } + } + } +} + +#if MICROPY_ENABLE_COMPILER + +mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_input_kind, mp_obj_dict_t *globals, mp_obj_dict_t *locals) { + // save context + mp_obj_dict_t *volatile old_globals = mp_globals_get(); + mp_obj_dict_t *volatile old_locals = mp_locals_get(); + + // set new context + mp_globals_set(globals); + mp_locals_set(locals); + + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, parse_input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, parse_input_kind == MP_PARSE_SINGLE_INPUT); + + mp_obj_t ret = MP_OBJ_NULL; + if (module_fun != MP_OBJ_NULL) { + if (MICROPY_PY_BUILTINS_COMPILE && globals == NULL) { + // for compile only, return value is the module function + ret = module_fun; + } else { + // execute module function and get return value + ret = mp_call_function_0(module_fun); + } + } + + // restore context and return value + mp_globals_set(old_globals); + mp_locals_set(old_locals); + return ret; +} + +#endif // MICROPY_ENABLE_COMPILER + +#if NO_NLR +void *m_malloc_fail(size_t num_bytes) { + DEBUG_printf("memory allocation failed, allocating %u bytes\n", (uint)num_bytes); + #if MICROPY_ENABLE_GC + if (gc_is_locked()) { + mp_raise_or_return_value( mp_obj_new_exception_msg(&mp_type_MemoryError, MP_ERROR_TEXT("memory allocation failed, heap is locked")), NULL); + } + #endif + mp_raise_or_return_value(mp_obj_new_exception_msg_varg(&mp_type_MemoryError, + MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)num_bytes), NULL); +} +#else +NORETURN void m_malloc_fail(size_t num_bytes) { + DEBUG_printf("memory allocation failed, allocating %u bytes\n", (uint)num_bytes); + #if MICROPY_ENABLE_GC + if (gc_is_locked()) { + mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("memory allocation failed, heap is locked")); + } + #endif + mp_raise_msg_varg(&mp_type_MemoryError, + MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)num_bytes); + for(;;){} +} +#endif + +#if NO_NLR +#else +#error "not valid for nlr" +void mp_raise_msg(const mp_obj_type_t *exc_type, const char *msg) { + if (msg == NULL) { + mp_raise_o(mp_obj_new_exception(exc_type)); + } else { + mp_raise_o(mp_obj_new_exception_msg(exc_type, msg)); + } +} + +void mp_raise_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + mp_obj_t exc = mp_obj_new_exception_msg_vlist(exc_type, fmt, args); + va_end(args); + nlr_raise(exc); +} + +void mp_raise_ValueError(const char *msg) { + mp_raise_msg(&mp_type_ValueError, msg); +} + +void mp_raise_TypeError(const char *msg) { + mp_raise_msg(&mp_type_TypeError, msg); +} + +void mp_raise_OSError(int errno_) { + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno_))); +} + +void mp_raise_NotImplementedError(const char *msg) { + mp_raise_msg(&mp_type_NotImplementedError, msg); +} +#endif + +#if MICROPY_STACK_CHECK || MICROPY_ENABLE_PYSTACK +void mp_raise_recursion_depth(void) { + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_RuntimeError, + MP_OBJ_NEW_QSTR(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded))); +} +#endif + +mp_obj_t mp_raise_o(mp_obj_t exc) { + //printf("mp_raise_o(%p)\n", MP_OBJ_TO_PTR(exc)); + // don't overwrite an existing exception + if (MP_STATE_THREAD(active_exception) == NULL) { + MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(exc); + } + return MP_OBJ_NULL; +} + +mp_obj_t mp_raise_msg_o(const mp_obj_type_t *exc_type, const char *msg) { + if (msg == NULL) { + return mp_raise_o(mp_obj_new_exception(exc_type)); + } else { + return mp_raise_o(mp_obj_new_exception_msg(exc_type, msg)); + } +} + +mp_obj_t mp_raise_ValueError_o(const char *msg) { + return mp_raise_msg_o(&mp_type_ValueError, msg); +} + +mp_obj_t mp_raise_TypeError_o(const char *msg) { + return mp_raise_msg_o(&mp_type_TypeError, msg); +} + +mp_obj_t mp_raise_NotImplementedError_o(const char *msg) { + return mp_raise_msg_o(&mp_type_NotImplementedError, msg); +} + +mp_obj_t mp_raise_OSError_o(int errno_) { + return mp_raise_o(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno_))); +} diff --git a/ports/wapy/runtime_no_nlr.h b/ports/wapy/runtime_no_nlr.h new file mode 100644 index 000000000..268d717c0 --- /dev/null +++ b/ports/wapy/runtime_no_nlr.h @@ -0,0 +1,223 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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_PY_RUNTIME_H +#define MICROPY_INCLUDED_PY_RUNTIME_H + +#include "py/mpstate.h" +#include "py/pystack.h" + +typedef enum { + MP_VM_RETURN_NORMAL, + MP_VM_RETURN_YIELD, + MP_VM_RETURN_EXCEPTION, +} mp_vm_return_kind_t; + +typedef enum { + MP_ARG_BOOL = 0x001, + MP_ARG_INT = 0x002, + MP_ARG_OBJ = 0x003, + MP_ARG_KIND_MASK = 0x0ff, + MP_ARG_REQUIRED = 0x100, + MP_ARG_KW_ONLY = 0x200, +} mp_arg_flag_t; + +typedef union _mp_arg_val_t { + bool u_bool; + mp_int_t u_int; + mp_obj_t u_obj; + mp_rom_obj_t u_rom_obj; +} mp_arg_val_t; + +typedef struct _mp_arg_t { + uint16_t qst; + uint16_t flags; + mp_arg_val_t defval; +} mp_arg_t; + +// Tables mapping operator enums to qstrs, defined in objtype.c +extern const byte mp_unary_op_method_name[]; +extern const byte mp_binary_op_method_name[]; + +void mp_init(void); +void mp_deinit(void); + +void mp_keyboard_interrupt(void); +void mp_handle_pending(bool raise_exc); +void mp_handle_pending_tail(mp_uint_t atomic_state); + +#if MICROPY_ENABLE_SCHEDULER +void mp_sched_lock(void); +void mp_sched_unlock(void); +static inline unsigned int mp_sched_num_pending(void) { + return MP_STATE_VM(sched_len); +} +bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg); +#endif + +// extra printing method specifically for mp_obj_t's which are integral type +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec); + +int mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig); +static inline int mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_min, size_t n_args_max, bool takes_kw) { + return mp_arg_check_num_sig(n_args, n_kw, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, takes_kw)); +} +int mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +int mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +void mp_arg_error_terse_mismatch(void); +void mp_arg_error_unimpl_kw(void); + +static inline mp_obj_dict_t *mp_locals_get(void) { + return MP_STATE_THREAD(dict_locals); +} +static inline void mp_locals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_locals) = d; +} +static inline mp_obj_dict_t *mp_globals_get(void) { + return MP_STATE_THREAD(dict_globals); +} +static inline void mp_globals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_globals) = d; +} + +mp_obj_t mp_load_name(qstr qst); +mp_obj_t mp_load_global(qstr qst); +mp_obj_t mp_load_build_class(void); +void mp_store_name(qstr qst, mp_obj_t obj); +void mp_store_global(qstr qst, mp_obj_t obj); +mp_obj_t mp_delete_name(qstr qst); +mp_obj_t mp_delete_global(qstr qst); + +mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg); +mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs); + +mp_obj_t mp_call_function_0(mp_obj_t fun); +mp_obj_t mp_call_function_1(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); +mp_obj_t mp_call_function_n_kw(mp_obj_t fun, size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw(size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_self_n_kw(mp_obj_t meth, mp_obj_t self, size_t n_args, size_t n_kw, const mp_obj_t *args); +// Call function and catch/dump exception - for Python callbacks from C code +// (return MP_OBJ_NULL in case of exception). +mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); + +typedef struct _mp_call_args_t { + mp_obj_t fun; + size_t n_args, n_kw, n_alloc; + mp_obj_t *args; +} mp_call_args_t; + +#if MICROPY_STACKLESS +// Takes arguments which are the most general mix of Python arg types, and +// prepares argument array suitable for passing to ->call() method of a +// function object (and mp_call_function_n_kw()). +// (Only needed in stackless mode.) +int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); +#endif + +mp_obj_t mp_unpack_sequence(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_unpack_ex(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_store_map(mp_obj_t map, mp_obj_t key, mp_obj_t value); +mp_obj_t mp_load_attr(mp_obj_t base, qstr attr); +void mp_convert_member_lookup(mp_obj_t obj, const mp_obj_type_t *type, mp_obj_t member, mp_obj_t *dest); +mp_obj_t mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catch_all_exc); +mp_obj_t mp_load_super_method(qstr attr, mp_obj_t *dest); +mp_obj_t mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t val); + +mp_obj_t mp_getiter(mp_obj_t o, mp_obj_iter_buf_t *iter_buf); +mp_obj_t mp_iternext_allow_raise(mp_obj_t o); // may return MP_OBJ_STOP_ITERATION instead of raising StopIteration() +mp_obj_t mp_iternext(mp_obj_t o); // will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration(...) +mp_obj_t mp_iternext2(mp_obj_t o); +bool mp_iternext_had_exc(void); +mp_vm_return_kind_t mp_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val); + +mp_obj_t mp_make_raise_obj(mp_obj_t o); + +mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level); +mp_obj_t mp_import_from(mp_obj_t module, qstr name); +void mp_import_all(mp_obj_t module); + +//#define mp_raise_type(exc_type) mp_raise_msg(exc_type, NULL) +//void mp_raise_msg(const mp_obj_type_t *exc_type, const char *msg); +void mp_raise_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); +void mp_raise_ValueError(const char *msg); +void mp_raise_TypeError(const char *msg); +void mp_raise_NotImplementedError(const char *msg); +void mp_raise_OSError(int errno_); +void mp_raise_recursion_depth(void); + +mp_obj_t mp_raise_o(mp_obj_t exc); +mp_obj_t mp_raise_msg_o(const mp_obj_type_t *exc_type, const char *msg); +mp_obj_t mp_raise_ValueError_o(const char *msg); +mp_obj_t mp_raise_TypeError_o(const char *msg); +mp_obj_t mp_raise_NotImplementedError_o(const char *msg); +mp_obj_t mp_raise_OSError_o(int errno_); + + +#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG +#undef mp_check_self +#define mp_check_self(pred) +#else +// A port may define to raise TypeError for example +#ifndef mp_check_self +#define mp_check_self(pred) assert(pred) +#endif +#endif + +// helper functions for native/viper code +int mp_native_type_from_qstr(qstr qst); +mp_uint_t mp_native_from_obj(mp_obj_t obj, mp_uint_t type); +mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type); + +#define mp_sys_path (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_path_obj))) +#define mp_sys_argv (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_argv_obj))) + +#if MICROPY_WARNINGS +#ifndef mp_warning +void mp_warning(const char *category, const char *msg, ...); +#endif +#else +#define mp_warning(...) +#endif + +#define mp_raise_or_return(exc) { return mp_raise_o(exc); } +#define mp_raise_or_return_value(exc, retval) { mp_raise_o(exc); return retval; } +#define mp_raise_OSError(errno) { return mp_raise_OSError_o(errno); } +#define mp_raise_OSError_or_return(errno, retval) { mp_raise_OSError_o(errno); return retval; } +#define mp_raise_type(extype) { return mp_raise_o(mp_obj_new_exception(extype)); } +#define mp_raise_type_or_return(extype, retval) { mp_raise_o(mp_obj_new_exception(extype)); return retval; } +#define mp_raise_ValueError(v) { return mp_raise_ValueError_o(v); } +#define mp_raise_msg(extype, mpt) { return mp_raise_msg_o( mp_obj_new_exception(extype), mpt ); } +#define mp_raise_TypeError(mpt) { return mp_raise_TypeError_o(mpt); } +#define mp_raise_TypeError_or_return(mpt, retval) { mp_raise_TypeError_o(mpt); return retval; } +#define mp_raise_NotImplementedError(mpt) { return mp_raise_NotImplementedError_o(mpt); } + + + +#endif // MICROPY_INCLUDED_PY_RUNTIME_H diff --git a/ports/wapy/stubs/emscripten.h b/ports/wapy/stubs/emscripten.h new file mode 100644 index 000000000..6749a8124 --- /dev/null +++ b/ports/wapy/stubs/emscripten.h @@ -0,0 +1,47 @@ +#ifndef EMSCRIPTEN_H + +#define EM_ASM_INT(...) 0 +#define EM_ASM(...) + +typedef void (*funcptr)(); + +#if __MAIN__ + +int emscripten_GetProcAddress(const char * name) { + return 0; +} + +int emscripten_run_preload_plugins(const char* file, char * onload, char * onerror) { + return 0; +} + +void emscripten_sleep(int s) { +} + + +static int emscripten_loop_run = 1; + +void emscripten_cancel_main_loop(){ + emscripten_loop_run = 0; +}; + +void emscripten_set_main_loop(funcptr emfunc, int a, int b){ + while(emscripten_loop_run){ + emfunc(); + } +} + +#else + +extern int emscripten_GetProcAddress(const char * name); +extern int emscripten_run_preload_plugins(const char* file, char * onload, char * onerror); +extern void emscripten_sleep(int s); +//extern int emscripten_loop_run; +extern void emscripten_cancel_main_loop(); +extern void emscripten_set_main_loop(funcptr emfunc, int a, int b); + +#endif + +#define EMSCRIPTEN_H + +#endif diff --git a/ports/wapy/upython.c b/ports/wapy/upython.c new file mode 100644 index 000000000..317accfc8 --- /dev/null +++ b/ports/wapy/upython.c @@ -0,0 +1,287 @@ +// +// core/main.c +// core/vfs.c + +#define HEAP_SIZE 64 * 1024 * 1024 + +// TODO: use a circular buffer for everything io related +#define MP_IO_SHM_SIZE 65535 + +#define MP_IO_SIZE 512 + +#define IO_KBD ( MP_IO_SHM_SIZE - (1 * MP_IO_SIZE) ) + + +#if MICROPY_ENABLE_PYSTACK +//#define MP_STACK_SIZE 16384 +#define MP_STACK_SIZE 32768 +static mp_obj_t pystack[MP_STACK_SIZE]; +#endif + +static char *stack_top; + +#include "../wapy/upython.h" + +#include "../wapy/core/ringbuf_o.h" +#include "../wapy/core/ringbuf_b.h" + + + +struct wPyInterpreterState i_main; + +struct wPyThreadState i_state; + +RBB_T(out_rbb, 2048); + + + +EMSCRIPTEN_KEEPALIVE int +show_os_loop(int state) { + int last = SHOW_OS_LOOP; + if (state >= 0) { + SHOW_OS_LOOP = state; + if (state > 0) { + + fprintf( +#if defined(__WASI__) + fd_logger, +#else + stdout, +#endif +"------------- showing os loop / starting repl --------------\n"); + //repl_started = 1; + } else { + if (last != state) + fprintf( +#if defined(__WASI__) + fd_logger, +#else + stdout, +#endif +"------------- hiding os loop --------------\n"); + } + } + return (last > 0); +} + +EMSCRIPTEN_KEEPALIVE int +state_os_loop(int state) { + static int last_state = -666; + + if (!state) { + last_state = SHOW_OS_LOOP; + SHOW_OS_LOOP = 0; + } else { + if (last_state != -666) + SHOW_OS_LOOP = last_state; + last_state = -666; + } + return last_state; +} + +// ---- + + +// should check null +char * +shm_ptr() { + return &i_main.shm_stdio[0]; +} + +char * +shm_get_ptr(int major, int minor) { + // keyboards + if (major == IO_KBD) { + if (minor == 0) + return &i_main.shm_stdio[IO_KBD]; + } + return NULL; +} + + + +char * +wPy_NewInterpreter() { + i_main.shm_stdio = (char *) malloc(MP_IO_SHM_SIZE); + if (!i_main.shm_stdio) + fprintf(stdout, "74:shm_stdio malloc failed\n"); + i_main.shm_stdio[0] = 0; + for (int i = 0; i < MP_IO_SHM_SIZE; i++) + i_main.shm_stdio[i] = 0; + i_state.interp = &i_main; + //pyexec_event_repl_init(); + return shm_ptr(); +} + +void +wPy_Initialize() { + int stack_dummy; + stack_top = (char *) &stack_dummy; + +#if MICROPY_ENABLE_GC + char *heap = (char *) malloc(HEAP_SIZE * sizeof(char)); + gc_init(heap, heap + HEAP_SIZE); +#endif + +//#if MICROPY_ENABLE_PYSTACK + mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); +//#endif + + mp_init(); + +#if MICROPY_EMIT_NATIVE + // Set default emitter options + MP_STATE_VM(default_emit_opt) = emit_opt; +#else + //(void)emit_opt; +#endif + + + mp_obj_list_init(mp_sys_path, 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); + mp_obj_list_init(mp_sys_argv, 0); +} + +// TODO py/stackctrl.c#L40 check stack way + + +#if !MICROPY_ENABLE_FINALISER +#error requires MICROPY_ENABLE_FINALISER (1) +#endif + + +#undef gc_collect + + + + +//extern void gc_collect_root(void **ptrs, size_t len); + +#include "py/gc.h" + +#undef gc_collect + + +// GC boundaries + + +// The address of the top of the stack when we first saw it +// This approximates a pointer to the bottom of the stack, but +// may not necessarily _be_ the exact bottom. This is set by +// the entry point of the application +static void *stack_initial = NULL; +// Pointer to the end of the stack +static uintptr_t stack_max = (uintptr_t) NULL; +// The current stack pointer +static uintptr_t stack_ptr_val = (uintptr_t) NULL; +// The amount of stack remaining +static ptrdiff_t stack_left = 0; + +// Maximum stack size of 248k +// This limit is arbitrary, but is what works for me under Node.js when compiled with emscripten +static size_t stack_limit = 1024 * 248 * 1; + + +void +gc_collect(void) { + + gc_dump_info(); + + stack_ptr_val = (uintptr_t) __builtin_frame_address(0); + + clog("gc_collect_start"); + + gc_collect_start(); + + size_t bottom = (uintptr_t) stack_ptr_val; + + void **ptrs = (void **) (void *) stack_ptr_val; + + size_t len = ((uintptr_t) stack_initial - bottom) / sizeof(uintptr_t); + + clog("gc_collect stack_initial=%p bottom=%zu len=%zu", stack_initial, bottom, len); + +//343 + + gc_collect_root(ptrs, len); +//343 + +#if MICROPY_PY_THREAD + mp_thread_gc_others(); +#endif +#if MICROPY_EMIT_NATIVE + mp_unix_mark_exec(); +#endif + + clog("gc_collect_end"); + gc_collect_end(); +} + +// #endif // gc + +int +pyeval(const char *src, mp_parse_input_kind_t input_kind) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), False); + + if (lex == NULL) { + fprintf(stdout, "148:NULL LEXER->handle_uncaught_exception\n%s\n", src); + return 0; + } + { +#if MICROPY_ENABLE_COMPILER + qstr source_name = lex->source_name; + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false); + if (module_fun != MP_OBJ_NULL) { + mp_obj_t ret = mp_call_function_0(module_fun); + if (ret != MP_OBJ_NULL) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + + } else { + return 1; //success + } + } + + } else { + // uncaught exception + fprintf(stdout, "150:NULL FUNCTION %s[%s]\n", qstr_str(source_name), src); + } +#else + #pragma message "compiler is disabled, no repl" +#endif + } + return 0; +} + +#if defined(__EMSCRIPTEN__) || defined(__WASI__) + #include "../wapy-wasm/wasm_mphal.c" +#endif + +#if __ANDROID__ + #include "../wapy-unix/aosp_mphal.c" +#endif + +int +PyRun_IO_CODE() { + return pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); +} + +int +PyRun_SimpleString(const char *command) { + int retval = 0; + if (command) { + retval = pyeval(command, MP_PARSE_FILE_INPUT); + } else { + if (i_main.shm_stdio[0]) { + retval = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT); + i_main.shm_stdio[0] = 0; + } + } + return retval; +} + + + diff --git a/ports/wapy/upython.h b/ports/wapy/upython.h new file mode 100644 index 000000000..6c38abe48 --- /dev/null +++ b/ports/wapy/upython.h @@ -0,0 +1,93 @@ +#ifndef UPYTHON_H + +#include + +#include "py/nlr.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/repl.h" +#include "py/gc.h" +#include "lib/utils/pyexec.h" +#include "py/mphal.h" + +#define IS_FILE 1 +#define IS_STR 0 + +#ifdef __EMSCRIPTEN__ + #include "emscripten.h" +#else + #define EMSCRIPTEN_KEEPALIVE +#endif + +//EMSCRIPTEN_KEEPALIVE void Py_InitializeEx(int param); +//EMSCRIPTEN_KEEPALIVE void PyRun_SimpleString(const char * code); + +#if WASM_FILE_API +EMSCRIPTEN_KEEPALIVE void PyRun_SimpleFile(FILE *fp, const char *filename); +EMSCRIPTEN_KEEPALIVE void PyRun_VerySimpleFile(const char *filename); +#endif + +int do_code(const char *src, int is_file); + +#define PATHLIST_SEP_CHAR ':' + +#if WAPY + + #if MICROPY_ENABLE_GC + static char heap[32*1024*1024]; + #endif + +//static int repl_started = -100; + +#endif + +#define REPL_INPUT_SIZE 16384 +#define REPL_INPUT_MAX REPL_INPUT_SIZE-1 + + +#define nullptr NULL +#define Py_RETURN_NONE return nullptr; +#define PyObject mp_obj_t + + +#include "core/ringbuf_b.h" +#include "core/ringbuf_o.h" + + +int PyArg_ParseTuple(PyObject *argv, const char *fmt, ...); + +extern int VMFLAGS_IF; +extern int SHOW_OS_LOOP; +extern int show_os_loop(int state); + + +struct +wPyInterpreterState { + char *shm_stdio ; + char *shm_input_event_0; +}; + + +struct +wPyThreadState { + struct wPyInterpreterState *interp; +}; + +extern struct wPyInterpreterState i_main ; +extern struct wPyThreadState i_state ; + +#if defined(__WASI__) + extern FILE *fd_logger; + #define cdbg(...) if (1){ fprintf(fd_logger, __VA_ARGS__ );fprintf(fd_logger, "\n"); } + #define clog(...) if (show_os_loop(-1)){ fprintf(fd_logger, __VA_ARGS__ );fprintf(fd_logger, "\n"); } +#else + #define cdbg(...) if (1){ fprintf(stderr, __VA_ARGS__ );fprintf(stderr, "\n"); } + #define clog(...) if (show_os_loop(-1)){ fprintf(stderr, __VA_ARGS__ );fprintf(stderr, "\n"); } +#endif + + + +#define UPYTHON_H 1 + +#endif //UPYTHON_H + diff --git a/ports/wapy/vmsl/vm_loop.c b/ports/wapy/vmsl/vm_loop.c new file mode 100644 index 000000000..9c12ca616 --- /dev/null +++ b/ports/wapy/vmsl/vm_loop.c @@ -0,0 +1,592 @@ + static size_t iolen; + + if (VMOP>= VMOP_PAUSE) { + VMOP--; + if ( (iolen = has_io()) ) { + // we have a chance to process pending Inputs, so ... + cdbg("syscall/pause WITH IO=%lu", iolen ); + if (noint_aio_fsync()>0) + goto VM_exhandler; + else { + IO_CODE_DONE; + } + + } else { + clog("syscall/pause -> io flush"); + // else just jump to flush Outputs + } + goto VM_syscall; + } + +// TODO: is it usefull ? + if ( (ENTRY_POINT != JMP_NONE) && !JUMPED_IN) { + clog("re-enter-on-entry %d => %d\n", ctx_current, CTX.pointer); + void* jump_entry; + jump_entry = ENTRY_POINT; + // Never to re-enter as this point. can only use the previous exit point. + JUMPED_IN = 1; + goto *jump_entry; + } + +// ========================================================================================== + // this call the async loop , no preemption should be allowed in there. + int aioerr = noint_aio_fsync(); + if (aioerr>0) + goto VM_exhandler; + + // do not erase stdin buffer in case it's repl or script data + if (!aioerr) { + IO_CODE_DONE; + } + +// ========================================================================================== + + // return to where we were going just before giving hand to host + if ( (EXIT_POINT != JMP_NONE) && JUMPED_IN) { + //cdbg("re-enter-on-exit IO=%lu", strlen(io_stdin) ); + + // was it gosub + if (JUMP_TYPE == TYPE_SUB) + RETURN; + + // was it branching + if (JUMP_TYPE == TYPE_JUMP) + COME_FROM; + } + +// All I/O stuff IS WRONG and should use a circular buffer. + + while (1){ + +/// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= + // process pending repl data + if (io_stdin[0]) { + //then it is toplevel or TODO: it's sync top level ( tranpiled by aio on the heap) + +/* + if (def_PyRun_SimpleString_is_repl) { + cdbg("REPL-INPUT-BEGIN[%s]", io_stdin); + } else { + cdbg("REPL-RAW-BEGIN[%s]", io_stdin); + } +*/ + // keep some space for moving labels + + + + + + + JUMP( def_PyRun_SimpleString, "main_iteration_repl"); + + + + + + + + if (RETVAL == MP_OBJ_NULL) { + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("64: RT Exception"); + goto VM_exhandler; + } + } + +/* + if (def_PyRun_SimpleString_is_repl) { + cdbg("REPL-INPUT-END"); + pyexec_repl_repl_restart(0); + } else { + cdbg("REPL-RAW-END"); + } +*/ + // mark done + IO_CODE_DONE; + // chances to have both stdin and repl input at same time are low + // so display the prompt + pyexec_repl_repl_restart(0); + } +/// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*= + //only flush kbd port on idle stdin + + char *keybuf; + keybuf = shm_get_ptr( IO_KBD, 0); + + def_PyRun_SimpleString_is_repl = false; + while (!KPANIC) { + // should give a way here to discard repl events feeding "await input()" instead + int rx = keybuf[0] ; //& 0xFF; + if (rx) { + + // if (rx==12) fprintf(stdout,"IO(12): Form Feed "); //clear screen + //if (rx>127) cdbg("FIXME:stdin-utf8:%u", rx ); + //pyexec_event_repl_process_char(rx); + if (pyexec_friendly_repl_process_char(rx)<0) { + cdbg("REPL-INPUT-SET[%s]", io_stdin); + def_PyRun_SimpleString_is_repl = true; + break; // goto repl loop execution + } + + *keybuf++ = 0; + } else break; // no more input + } + + // always reset io buffer no matter what since io_stdin can overwrite it in script mode + keybuf[0]=0; + + // exit repl loop + if (!io_stdin[0]) + break; + } + + + if (VMOP==VM_HCF) { +VM_stackmess:; + puts("no guru meditation, bye"); + #if !ASYNCIFY + emscripten_cancel_main_loop(); + #endif + } + + +goto VM_syscall; +//================================================================== + + +// def PyRun_SimpleString(const_char_p src) -> int; +def_PyRun_SimpleString: { +//return: + // should set a global integer + //int ret = 0; +//args: + char* src = i_main.shm_stdio; + mp_parse_input_kind_t input_kind = MP_PARSE_FILE_INPUT; + +//vars + +//code + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + + //default is return EX + mp_obj_t exret = MP_OBJ_NULL; + + if (lex == NULL) { + clog("113:syntax error"); + //handle_uncaught_exception(); + } else { + qstr source_name = lex->source_name; + + + #if MICROPY_PY___FILE__ + if (input_kind == MP_PARSE_FILE_INPUT) { + mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); + } + #endif + + mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); + + mp_obj_t module_fun = mp_compile(&parse_tree, source_name, def_PyRun_SimpleString_is_repl); + + + if ( module_fun != MP_OBJ_NULL) { + //STACKLESS STARTS HERE + + // unlock I/O buffer + IO_CODE_DONE; + + CTX.self_in = module_fun; + CTX.n_args = 0; + CTX.n_kw = 0; + CTX.args = NULL ; + + const mp_obj_type_t *type = mp_obj_get_type(CTX.self_in); + + exret = MP_OBJ_NULL; + + if (type->call != NULL) { + if ( (int)*type->call == (int)&fun_bc_call ) { + // >>> + ctx_get_next(CTX_COPY); + GOSUB(def_func_bc_call, "mp_call_function_n_kw"); + exret = SUBVAL; //CTX.sub_value; + // <<< + } else { + clog("151: native call %p %p ?", *type->call , fun_bc_call); + exret = type->call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + } + + } else { + mp_raise_o( + mp_obj_new_exception_msg_varg( + &mp_type_TypeError,"'%s' object isn't callable", mp_obj_get_type_str(CTX.self_in) + ) + ); + } + + if ( exret != MP_OBJ_NULL ) { + if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { + clog("645: PENDING EXCEPTION CLEARED AND RAISED"); + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + mp_raise_o(obj); + } + } + + } + + // ex check + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("178: uncaught exception"); + } + + } + RETVAL = exret ; + COME_FROM; +} // PyRun_SimpleString + + +#define VM_CANCEL_ACTIVE_FINALLY(sptr) do { \ + if (mp_obj_is_small_int(sptr[-1])) { \ + /* Stack: (..., prev_dest_ip, prev_cause, dest_ip) */ \ + /* Cancel the unwind through the previous finally, replace with current one */ \ + sptr[-2] = sptr[0]; \ + sptr -= 2; \ + } else { \ + assert(sptr[-1] == mp_const_none || mp_obj_is_exception_instance(sptr[-1])); \ + /* Stack: (..., None/exception, dest_ip) */ \ + /* Silence the finally's exception value (may be None or an exception) */ \ + sptr[-1] = sptr[0]; \ + --sptr; \ + } \ +} while (0) + + +#if MICROPY_PY_SYS_SETTRACE + +#define FRAME_SETUP() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + MP_STATE_THREAD(current_code_state) = CTX.code_state; \ + assert(CTX.code_state != CTX.code_state->prev_state); \ +} while(0) + +#define FRAME_ENTER() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + CTX.code_state->prev_state = MP_STATE_THREAD(current_code_state); \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + if (!mp_prof_is_executing) { \ + mp_prof_frame_enter(CTX.code_state); \ + } \ +} while(0) + +#define FRAME_LEAVE() do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + MP_STATE_THREAD(current_code_state) = CTX.code_state->prev_state; \ + assert(CTX.code_state != CTX.code_state->prev_state); \ +} while(0) + +#define FRAME_UPDATE() do { \ + assert(MP_STATE_THREAD(current_code_state) == CTX.code_state); \ + if (!mp_prof_is_executing) { \ + CTX.code_state->frame = MP_OBJ_TO_PTR(mp_prof_frame_update(CTX.code_state)); \ + } \ +} while(0) + +#define TRACE_TICK(current_ip, current_sp, is_exception) do { \ + assert(CTX.code_state != CTX.code_state->prev_state); \ + assert(MP_STATE_THREAD(current_code_state) == CTX.code_state); \ + if (!mp_prof_is_executing && CTX.code_state->frame && MP_STATE_THREAD(prof_trace_callback)) { \ + MP_PROF_INSTR_DEBUG_PRINT(code_state->ip); \ + } \ + if (!mp_prof_is_executing && CTX.code_state->frame && CTX.code_state->frame->callback) { \ + mp_prof_instr_tick(CTX.code_state, is_exception); \ + } \ +} while(0) + +#else // MICROPY_PY_SYS_SETTRACE + + #define FRAME_SETUP() + #define FRAME_ENTER() + #define FRAME_LEAVE() + #define FRAME_UPDATE() + #define TRACE_TICK(current_ip, current_sp, is_exception) + +#endif // MICROPY_PY_SYS_SETTRACE + + +def_mp_call_function_n_kw: { + const mp_obj_type_t *type = mp_obj_get_type(CTX.self_in); + + if (type->call != NULL) { + if ( (int)*type->call == (int)&fun_bc_call ) { + // >>> + ctx_get_next(CTX_COPY); + GOSUB(def_func_bc_call, "mp_call_function_n_kw"); + RETVAL = SUBVAL; //CTX.sub_value; + // <<< + } else { + clog(" 899: native call %p %p", *type->call , &fun_bc_call); +#if VMTRACE +clog(" 899: native call"); +#endif + RETVAL = type->call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + } + + } else { + clog("919:def_mp_call_function_n_kw ex!"); + mp_raise_o( + mp_obj_new_exception_msg_varg( + &mp_type_TypeError,"'%s' object isn't callable", mp_obj_get_type_str(CTX.self_in) + ) + ); + RETVAL = MP_OBJ_NULL; + } + + RETURN; +} + + +#define VM_DECODE_CODESTATE_SIZE(bytecode, n_state_out_var, state_size_out_var) \ + { \ + n_state_out_var = mp_decode_uint_value(bytecode); \ + size_t n_exc_stack = mp_decode_uint_value(mp_decode_uint_skip(bytecode)); \ + \ + state_size_out_var = n_state_out_var * sizeof(mp_obj_t) \ + + n_exc_stack * sizeof(mp_exc_stack_t); \ + } + + + + +/* NOT OK + + when a function has tuple with default value init style the state_size calculated by + VM_DECODE_CODESTATE_SIZE is just huge and wrong. + +*/ + +//266:objfun.c +def_func_bc_call: { + + RETVAL = MP_OBJ_NULL; + + if (MP_STACK_CHECK()) { + clog("974:def_func_bc_call: MP_STACK_CHECK ex!"); + goto def_func_bc_call_ret; + } + + CTX.self_fun = MP_OBJ_TO_PTR(CTX.self_in); + + VM_DECODE_CODESTATE_SIZE(CTX.self_fun->bytecode, CTX.n_state, CTX.state_size); + + + // allocate state for locals and stack + // new frame == new code state. + if ( CTX.state_size > 41360 ) { +#define TRACE_ON (1) + #include "vmsl/vmbc_trace.c" +#undef TRACE_ON + cdbg("882:BUG: =======> start=%p cur=%p end=%p state_size=%ld", + MP_STATE_THREAD(pystack_start), MP_STATE_THREAD(pystack_cur), MP_STATE_THREAD(pystack_end) + , CTX.state_size); + +#pragma message "Silly workaround for func with tuple init" +#if 0 + if (CTX.code_state = fun_bc_call_pre(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args) ){ +#if 1 + CTX.vm_return_kind = mp_execute_bytecode(CTX.code_state, MP_OBJ_NULL); +#else + ctx_get_next(CTX_COPY); + NEXT.inject_exc = MP_OBJ_NULL; + NEXT.ip = NEXT.code_state->ip; + NEXT.sp = NEXT.code_state->sp; + GOSUB(def_mp_execute_bytecode,"func_bc_call"); + CTX.vm_return_kind = CTX.sub_vm_return_kind; +#endif + RETVAL = fun_bc_call_past(CTX.code_state, CTX.vm_return_kind, CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); +#endif + RETVAL = fun_bc_call(CTX.self_in, CTX.n_args, CTX.n_kw, CTX.args); + goto def_func_bc_call_ret; + } + + CTX.code_state = mp_pystack_alloc(sizeof(mp_code_state_t) + CTX.state_size); + + if (!CTX.code_state) { + clog("908:def_func_bc_call: MP_PYSTACK_ALLOC ex!"); + goto def_func_bc_call_ret; + } + + CTX.inject_exc = MP_OBJ_NULL ; + CTX.code_state->fun_bc = CTX.self_fun; + CTX.code_state->ip = 0; + CTX.code_state->n_state = CTX.n_state; + + clog("917:TODO: can we save old_globals before this call ?"); + + mp_obj_t ret = mp_setup_code_state(CTX.code_state, CTX.n_args, CTX.n_kw, CTX.args); + + //? + CTX.code_state->old_globals = mp_globals_get(); + + + if ( ret == MP_OBJ_NULL) { + clog("999:def_func_bc_call: INIT_CODESTATE ex!"); + mp_nonlocal_free(CTX.code_state, sizeof(mp_code_state_t)); + goto def_func_bc_call_ret; + } + + ctx_get_next(CTX_COPY); + + // execute the byte code with the correct globals context + mp_globals_set(NEXT.self_fun->globals); + + + if (VMFLAGS_IF>0) { // FIXED ! + clog("132:unwrap.c ALLOWINT def_func_bc_call->def_mp_execute_bytecode"); + + // ip sp would not be set on NEXT + NEXT.ip = NEXT.code_state->ip; + NEXT.sp = NEXT.code_state->sp; + + GOSUB(def_mp_execute_bytecode,"func_bc_call"); + CTX.vm_return_kind = CTX.sub_vm_return_kind; + + } else { + clog("136:unwrap.c NOINTERRUPT"); + ctx_abort(); //128 + CTX.vm_return_kind = mp_execute_bytecode(CTX.code_state, CTX.inject_exc); + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { + CTX.sub_value= *CTX.code_state->sp; + } else { + if(CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) + CTX.sub_value= CTX.code_state->state[0]; + else + clog("1031:unwrap.c unrouted .vm_return_kind") + } + + } + + mp_globals_set(CTX.code_state->old_globals); + mp_pystack_free(CTX.code_state); + + #if MICROPY_DEBUG_VM_STACK_OVERFLOW + #error "[...]" + #endif + +//353:objfun.c + // work in done juste before def_mp_execute_bytecode RETURN + if (CTX.vm_return_kind == MP_VM_RETURN_NORMAL) { + RETVAL = CTX.sub_value; + } else { + if(CTX.vm_return_kind == MP_VM_RETURN_EXCEPTION) + RETVAL = mp_raise_o(CTX.sub_value); + else + clog("1050:unwrap.c unrouted .sub_vm_return_kind") + } + + +def_func_bc_call_ret: + RETURN; +} + + +#include "vmsl/unwrap.c" + + +//================================================================== +// VM_syscall_verbose:; +// puts("-syscall-"); + +VM_exhandler:; + // IO_CODE_DONE; follows + if (MP_STATE_THREAD(active_exception) != NULL) { + clog("501: uncaught exception") + //mp_hal_set_interrupt_char(-1); + mp_handle_pending(false); + if (uncaught_exception_handler()) { + clog("651:SystemExit"); + } else { + clog("653: exception done"); + } + async_loop = 0; + } + + if (def_PyRun_SimpleString_is_repl) { + //cdbg("EX-REPL-INPUT-END"); + pyexec_repl_repl_restart(0); + } /* else { + cdbg("EX-END"); + } */ + + +VM_syscall:; + IO_CODE_DONE; +// TODO: flush all at once + // STDOUT flush before eventually filling it again + +#if defined(__EMSCRIPTEN__) + + // use json { "channel" : "hexdata" }\n + + if (!rbb_is_empty(&out_rbb)) { + // flush stdout + unsigned char out_c = 0; + fprintf(cc, "{\"%c\":\"", 48+1); + //TODO put a 0 at end and printf buffer directly + while (rbb_pop(&out_rbb, &out_c)) + fprintf(cc, "%c", out_c ); + fprintf(cc, "\"}\n"); + } +#else + + // @channel:hexdata\n + + if (!rbb_is_empty(&out_rbb)) { + int cnt = 0; + unsigned char out_c = 0; + + //TODO put a 0 at end and printf buffer directly via shm + + while (rbb_pop(&out_rbb, &out_c)) { + if (!cnt--) { + fprintf(cc, "\n@%c:%c", 48+1,out_c); + cnt = 251; + } else + fprintf(cc, "%c", out_c ); + } + fprintf(cc, "\n"); + } + +#endif + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// diff --git a/ports/wapy/wapy.c b/ports/wapy/wapy.c new file mode 100644 index 000000000..ed56a50ee --- /dev/null +++ b/ports/wapy/wapy.c @@ -0,0 +1,154 @@ +#include "py/mpstate.h" + +mp_state_ctx_t mp_state_ctx; + +#if defined(__EMSCRIPTEN__) + #define fd_logger stderr + #if defined(__WASI__) + #error "wasi-emscripten" + #endif +#endif + +STATIC void stderr_print_strn2(void *env, const char *str, size_t len) { + (void)env; + mp_hal_stdout_tx_strn(str,len); +} + +const mp_print_t mp_stderr_print2 = {NULL, stderr_print_strn2}; + +#if 0 + int uncaught_exception_handler(void) { + mp_obj_base_t *exc = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // None is an exit value of 0; an int is its value; anything else is 1 + /* + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + */ + #if defined(__EMSCRIPTEN__) + EM_ASM({console.log("91:SystemExit");}); + #endif + + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + // Report all other exceptions + cdbg("mp_stderr_print2=%p",exc) + cdbg("mp_obj=%p", MP_OBJ_FROM_PTR(exc) ); + mp_obj_print_exception(&mp_stderr_print2, MP_OBJ_FROM_PTR(exc)); + return 0; + } +#else + // user defined exception handler + int uncaught_exception_handler(void) { + mp_obj_base_t *ex = MP_STATE_THREAD(active_exception); + // check for SystemExit + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(ex->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { + // None is an exit value of 0; an int is its value; anything else is 1 + /* + mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(exc)); + mp_int_t val = 0; + if (exit_val != mp_const_none && !mp_obj_get_int_maybe(exit_val, &val)) { + val = 1; + } + return FORCED_EXIT | (val & 255); + */ + clog("499:SystemExit"); + return 1; + } + MP_STATE_THREAD(active_exception) = NULL; + + // fake sys.excepthook via pythons.__init__ + cdbg("57: *** EXCEPTION ***\n"); + pyv( mp_obj_get_type(ex) ); + pyv( MP_OBJ_FROM_PTR(ex) ); + pyv( MP_ROM_NONE ); + pycore("pyc_excepthook"); + return 0; + } +#endif + +void +dump_args2(const mp_obj_t *a, size_t sz) { + cdbg("331: %p: ", a); + for (size_t i = 0; i < sz; i++) { + fprintf(fd_logger,"%p ", a[i]); + } + fprintf(fd_logger,"\n"); +} + + + +// this is reserved to max speed asynchronous code + +int +noint_aio_fsync() { + + if (!io_stdin[0]) + return -1; + + if (!endswith(io_stdin, "#aio.step\n")) + return -1; + + int ex=-1; + async_state = VMFLAGS_IF; + // CLI + VMFLAGS_IF = 0; + + //TODO: maybe somehow consumme kbd data for async inputs ? + //expect script to be properly async programmed and run them full speed via C stack ? + + if (async_loop) { + + if ( (async_loop = pyeval(i_main.shm_stdio, MP_PARSE_FILE_INPUT)) ) { + ex=0; + } else { + fprintf(stdout, "ERROR[%s]\n", io_stdin); + // ex check + ex=1; + } + + } + + // STI + VMFLAGS_IF = async_state; + return ex; +} + + + +int +endswith(const char * str, const char * suffix) { + int str_len = strlen(str); + int suffix_len = strlen(suffix); + + return + (str_len >= suffix_len) && (0 == strcmp(str + (str_len-suffix_len), suffix)); +} + + + +// do not run lines starting with # +size_t +has_io() { + size_t check = strlen(io_stdin); + if (io_stdin[0] && (check != 38)) + return check; + return 0; +} + + + + + + + + + + +// diff --git a/ports/wapy/wapy.h b/ports/wapy/wapy.h new file mode 100644 index 000000000..4fe2ab364 --- /dev/null +++ b/ports/wapy/wapy.h @@ -0,0 +1,69 @@ + +//need global interrupt state marker ( @syscall / @awaited / aio_suspend ) +// to choose which VM to enter at top level +// this is different from ctx interrupts ctx_if ( if used ) + +int VMFLAGS_IF = 0; +int SHOW_OS_LOOP=0; +struct timespec t_timespec; +struct timeval t_timeval; + + +#include "../wapy/core/fdfile.h" + +#include "py/compile.h" +#include "py/emitglue.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/parse.h" +#include "py/bc0.h" +// overwrite the "math" in bytecode value with plain integers +// #include "bc_as_integers.h" +#include "py/bc.h" +#include "py/repl.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "lib/utils/pyexec.h" + + +// for mp_call_function_0 +#include "py/parsenum.h" +#include "py/compile.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objlist.h" +#include "py/objmodule.h" // <= function defined in +#include "py/objgenerator.h" +#include "py/smallint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/stackctrl.h" +#include "py/gc.h" + +#include "upython.h" + +#define __MAIN__ (1) +#include "emscripten.h" +#undef __MAIN__ + +extern int emscripten_GetProcAddress(const char * name); + +int uncaught_exception_handler(void); + +void dump_args2(const mp_obj_t *a, size_t sz); + +int noint_aio_fsync(); + +int endswith(const char * str, const char * suffix); + +size_t has_io(); + +int PyArg_ParseTuple(PyObject *argv, const char *fmt, ...) { + va_list argptr; + va_start (argptr, fmt ); + vfprintf(stdout,fmt,argptr); + va_end (argptr); + return 0; +} + + diff --git a/ports/wapy/wapy.mk b/ports/wapy/wapy.mk new file mode 100644 index 000000000..417114076 --- /dev/null +++ b/ports/wapy/wapy.mk @@ -0,0 +1,130 @@ + +ifdef NDK + CC=$(NDK_CC) -march=armv7-a -mthumb --target=armv7-none-linux-androideabi19 --gcc-toolchain=$(NDK)/toolchains/llvm/prebuilt/linux-x86_64 --sysroot=$(NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 + + LD=$(NDK_LD) -L$(NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/19 + CFLAGS += -DANDROID_PLATFORM=android-19 -DAPP_PLATFORM=android-19 -D__ANDROID_API__=19 + LD_EXTRA += -fuse-ld=lld -L/data/cross/pydk/aosp/apkroot-armeabi-v7a/usr/lib -lffi -ldl -lm -lc -landroid + INC += -I/data/cross/pydk/aosp/apkroot-armeabi-v7a/usr/include -I../wapy/stubs + OG = -O0 -g3 + LD_SHARED = -shared +else + LD_EXTRA += -s FORCE_FILESYSTEM=1 --use-preload-plugins + LD_EXTRA += -L/data/cross/pydk/wasm/apkroot-wasm/usr/lib -lSDL2 -logg +endif + + +# py object files +PY_CORE_O_BASENAME = $(addprefix py/,\ + mpstate.o \ + malloc.o \ + gc.o \ + pystack.o \ + qstr.o \ + vstr.o \ + mpprint.o \ + unicode.o \ + mpz.o \ + reader.o \ + lexer.o \ + parse.o \ + scope.o \ + compile.o \ + emitcommon.o \ + emitbc.o \ + asmbase.o \ + asmx64.o \ + emitnx64.o \ + asmx86.o \ + emitnx86.o \ + asmthumb.o \ + emitnthumb.o \ + emitinlinethumb.o \ + asmarm.o \ + emitnarm.o \ + asmxtensa.o \ + emitnxtensa.o \ + emitinlinextensa.o \ + emitnxtensawin.o \ + formatfloat.o \ + parsenumbase.o \ + parsenum.o \ + emitglue.o \ + persistentcode.o \ + ../ports/wapy/runtime_no_nlr.o \ + runtime_utils.o \ + scheduler.o \ + nativeglue.o \ + pairheap.o \ + ringbuf.o \ + stackctrl.o \ + ../ports/wapy/argcheck_no_nlr.o \ + warning.o \ + profile.o \ + map.o \ + obj.o \ + ../ports/wapy/objarray_no_nlr.o \ + objattrtuple.o \ + objbool.o \ + objboundmeth.o \ + objcell.o \ + objclosure.o \ + objcomplex.o \ + objdeque.o \ + ../ports/wapy/objdict_no_nlr.o \ + objenumerate.o \ + objexcept.o \ + objfilter.o \ + objfloat.o \ + ../ports/wapy/objfun_no_nlr.o \ + objgenerator.o \ + objgetitemiter.o \ + objint.o \ + objint_longlong.o \ + objint_mpz.o \ + ../ports/wapy/objlist_no_nlr.o \ + objmap.o \ + objmodule.o \ + objobject.o \ + objpolyiter.o \ + objproperty.o \ + objnone.o \ + objnamedtuple.o \ + objrange.o \ + objreversed.o \ + objset.o \ + objsingleton.o \ + objslice.o \ + ../ports/wapy/objstr_no_nlr.o \ + objstrunicode.o \ + objstringio.o \ + objtuple.o \ + objtype.o \ + objzip.o \ + opmethods.o \ + sequence.o \ + stream.o \ + binary.o \ + ../ports/wapy/builtinimport_no_nlr.o \ + builtinevex.o \ + builtinhelp.o \ + modarray.o \ + ../ports/wapy/modbuiltins_no_nlr.o \ + modcollections.o \ + modgc.o \ + modio.o \ + modmath.o \ + modcmath.o \ + modmicropython.o \ + modstruct.o \ + modsys.o \ + moduerrno.o \ + modthread.o \ + vm.o \ + bc.o \ + showbc.o \ + repl.o \ + smallint.o \ + frozenmod.o \ + ) + diff --git a/ports/wapy/wasm_mphal.c b/ports/wapy/wasm_mphal.c new file mode 100644 index 000000000..a8306f8b5 --- /dev/null +++ b/ports/wapy/wasm_mphal.c @@ -0,0 +1,157 @@ +#include +#include +#include + +#include + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/misc.h" + +#include + +#ifdef __EMSCRIPTEN__ +#include "emscripten.h" +#elif __CPP__ + #define EMSCRIPTEN_KEEPALIVE +#endif + +#include "../wapy/upython.h" + +#include + +void mp_hal_delay_us(mp_uint_t us) { + clog("mp_hal_delay_us(%u)", us ); +} + +void mp_hal_delay_ms(mp_uint_t ms) { + mp_hal_delay_us(ms*1000); +} + + + + +struct timespec ts; +#define EPOCH_US 0 +#if EPOCH_US +static unsigned long epoch_us = 0; +#endif + +mp_uint_t mp_hal_ticks_ms(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us -1000; + return (mp_uint_t)( (now_us - epoch_us) / 1000 ) ; +#else + return (mp_uint_t)(now_us / 1000); +#endif + +} + +mp_uint_t mp_hal_ticks_us(void) { + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long now_us = ts.tv_sec * 1000000 + ts.tv_nsec / 1000 ; +#if EPOCH_US + if (!epoch_us) + epoch_us = now_us-1; + return (mp_uint_t)(now_us - epoch_us); +#else + return (mp_uint_t)now_us; +#endif +} + + + + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + + fprintf(stderr,"mp_hal_stdin_rx_chr"); + unsigned char c = fgetc(stdin); + return c; +} + +static unsigned char last = 0; + +extern rbb_t out_rbb; + +unsigned char v2a(int c) +{ + const unsigned char hex[] = "0123456789abcdef"; + return hex[c]; +} + +unsigned char hex_hi(unsigned char b) { + return v2a((b >> 4) & 0x0F); +} +unsigned char hex_lo(unsigned char b) { + return v2a((b) & 0x0F); +} + +unsigned char out_push(unsigned char c) { + if (last>127) { + if (c>127) + fprintf(stderr," -- utf-8(2/2) %u --\n", c ); + } else { + if (c>127) + fprintf(stderr," -- utf-8(1/2) %u --\n", c ); + } + rbb_append(&out_rbb, hex_hi(c)); + rbb_append(&out_rbb, hex_lo(c)); + return (unsigned char)c; +} + + + +//FIXME: libc print with valid json are likely to pass and get interpreted by pts +//TODO: buffer all until render tick + +//this one (over)cooks like _cooked +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + for(int i=0;i$(PySrcDir)qstrdefs.h $(DestDir)qstrdefscollected.h $(DestDir)qstrdefs.generated.h + $(MICROPY_CPYTHON3) python cl.exe $([System.IO.Path]::Combine(`$(CLToolPath)`, `$(CLToolExe)`)) diff --git a/ports/windows/windows_mphal.c b/ports/windows/windows_mphal.c index 8ed087358..49daa0542 100644 --- a/ports/windows/windows_mphal.c +++ b/ports/windows/windows_mphal.c @@ -255,3 +255,15 @@ 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; +} + +// 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); +} diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 50cd031d4..08868d716 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -1,24 +1,118 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright 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. + cmake_minimum_required(VERSION 3.13.1) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) -project(NONE) - -target_sources(app PRIVATE src/zephyr_start.c src/zephyr_getchar.c) - -add_library(libmicropython STATIC IMPORTED) -set_target_properties(libmicropython PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/libmicropython.a) -target_link_libraries(app PUBLIC libmicropython) - -zephyr_get_include_directories_for_lang_as_string(C includes) -zephyr_get_system_include_directories_for_lang_as_string(C system_includes) -zephyr_get_compile_definitions_for_lang_as_string(C definitions) -zephyr_get_compile_options_for_lang_as_string(C options) - -add_custom_target( - outputexports - COMMAND echo CC="${CMAKE_C_COMPILER}" - COMMAND echo AR="${CMAKE_AR}" - COMMAND echo Z_CFLAGS=${system_includes} ${includes} ${definitions} ${options} - VERBATIM - USES_TERMINAL +project(micropython) + +set(MICROPY_PORT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(MICROPY_DIR ${MICROPY_PORT_DIR}/../..) +set(MICROPY_TARGET micropython) + +include(${MICROPY_DIR}/py/py.cmake) +include(${MICROPY_DIR}/extmod/extmod.cmake) + +set(MICROPY_SOURCE_PORT + main.c + help.c + machine_i2c.c + machine_pin.c + machine_uart.c + modmachine.c + moduos.c + modusocket.c + modutime.c + modzephyr.c + modzsensor.c + uart_core.c + zephyr_storage.c +) +list(TRANSFORM MICROPY_SOURCE_PORT PREPEND ${MICROPY_PORT_DIR}/) + +set(MICROPY_SOURCE_LIB + timeutils/timeutils.c + utils/mpirq.c + utils/stdout_helpers.c + utils/printf.c + utils/pyexec.c + utils/interrupt_char.c + mp-readline/readline.c + oofatfs/ff.c + oofatfs/ffunicode.c + littlefs/lfs1.c + littlefs/lfs1_util.c + littlefs/lfs2.c + littlefs/lfs2_util.c ) +list(TRANSFORM MICROPY_SOURCE_LIB PREPEND ${MICROPY_DIR}/lib/) + +set(MICROPY_SOURCE_QSTR + ${MICROPY_SOURCE_PY} + ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_LIB} + ${MICROPY_SOURCE_PORT} +) + +zephyr_get_include_directories_for_lang(C includes) +zephyr_get_system_include_directories_for_lang(C system_includes) +zephyr_get_compile_definitions_for_lang(C definitions) +zephyr_get_compile_options_for_lang(C options) + +set(MICROPY_CPP_FLAGS_EXTRA ${includes} ${system_includes} ${definitions} ${options}) + +zephyr_library_named(${MICROPY_TARGET}) + +zephyr_library_include_directories( + ${MICROPY_DIR} + ${MICROPY_PORT_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +zephyr_library_compile_options( + -std=gnu99 -fomit-frame-pointer +) + +zephyr_library_compile_definitions( + NDEBUG + MP_CONFIGFILE=<${CONFIG_MICROPY_CONFIGFILE}> + MICROPY_HEAP_SIZE=${CONFIG_MICROPY_HEAP_SIZE} + FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\" + MICROPY_VFS_FAT=$ + MICROPY_VFS_LFS1=$ + MICROPY_VFS_LFS2=$ +) + +zephyr_library_sources(${MICROPY_SOURCE_QSTR}) + +add_dependencies(${MICROPY_TARGET} zephyr_generated_headers) + +include(${MICROPY_DIR}/py/mkrules.cmake) + +target_sources(app PRIVATE + src/zephyr_start.c + src/zephyr_getchar.c +) + +target_link_libraries(app PRIVATE ${MICROPY_TARGET}) diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig new file mode 100644 index 000000000..5545cf3b1 --- /dev/null +++ b/ports/zephyr/Kconfig @@ -0,0 +1,46 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright 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. + +menu "MicroPython Options" + +config MICROPY_CONFIGFILE + string "Configuration file" + default "mpconfigport_minimal.h" + +config MICROPY_HEAP_SIZE + int "Heap size" + default 16384 + +config MICROPY_VFS_FAT + bool "FatFS file system" + +config MICROPY_VFS_LFS1 + bool "LittleFs version 1 file system" + +config MICROPY_VFS_LFS2 + bool "LittleFs version 2 file system" + +endmenu # MicroPython Options + +source "Kconfig.zephyr" diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index a2fb393d9..cdc3c70f0 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 v2.5.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,59 +38,60 @@ 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.5.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.5.0 $ west update With Zephyr installed you may then need to configure your environment, for example by sourcing `zephyrproject/zephyr/zephyr-env.sh`. -Once Zephyr is ready to use you can build the MicroPython port. -In the port subdirectory `ports/zephyr/` run: +Once Zephyr is ready to use you can build the MicroPython port just like any +other Zephyr application. You can do this anywhere in your file system, it does +not have to be in the `ports/zephyr` directory. Assuming you have cloned the +MicroPython repository into your home directory, you can build the Zephyr port +for a frdm_k64f board like this: - $ make BOARD= + $ west build -b frdm_k64f ~/micropython/ports/zephyr -If you don't specify BOARD, the default is `qemu_x86` (x86 target running -in QEMU emulator). Consult the Zephyr documentation above for the list of +To build for QEMU instead: + + $ west build -b qemu_x86 ~/micropython/ports/zephyr + +Consult the Zephyr documentation above for the list of supported boards. Board configuration files appearing in `ports/zephyr/boards/` correspond to boards that have been tested with MicroPython and may have additional options enabled, like filesystem support. - Running ------- -To run the resulting firmware in QEMU (for BOARDs like qemu_x86, -qemu_cortex_m3): +To flash the resulting firmware to your board: - make run + $ west flash -With the default configuration, networking is now enabled, so you need to -follow instructions in https://wiki.zephyrproject.org/view/Networking-with-Qemu -to setup host side of TAP/SLIP networking. If you get error like: +Or, to flash it to your board and start a gdb debug session: - could not connect serial device to character backend 'unix:/tmp/slip.sock' + $ west debug -it's a sign that you didn't followed instructions above. If you would like -to just run it quickly without extra setup, see "minimal" build below. +To run the resulting firmware in QEMU (for BOARDs like qemu_x86, +qemu_cortex_m3): -For deploying/flashing a firmware on a real board, follow Zephyr -documentation for a given board, including known issues for that board -(if any). (Mind again that networking is enabled for the default build, -so you should know if there're any special requirements in that regard, -cf. for example QEMU networking requirements above; real hardware boards -generally should not have any special requirements, unless there're known -issues). + $ west build -t run -For example, to deploy firmware on the FRDM-K64F board run: +Networking is enabled with the default configuration, so you need to follow +instructions in +https://docs.zephyrproject.org/latest/guides/networking/qemu_setup.html#networking-with-qemu +to setup the host side of TAP/SLIP networking. If you get an error like: - $ make BOARD=frdm_k64f flash + could not connect serial device to character backend 'unix:/tmp/slip.sock' +it's a sign that you didn't follow the instructions above. If you would like +to just run it quickly without extra setup, see "minimal" build below. Quick example ------------- @@ -151,9 +152,10 @@ enabled over time. To make a minimal build: - ./make-minimal BOARD= + $ west build -b qemu_x86 ~/micropython/ports/zephyr -- -DCONF_FILE=prj_minimal.conf To run a minimal build in QEMU without requiring TAP networking setup -run the following after you built image with the previous command: +run the following after you built an image with the previous command: + + $ west build -t run - ./make-minimal BOARD= run diff --git a/ports/zephyr/boards/nucleo_h743zi.conf b/ports/zephyr/boards/nucleo_h743zi.conf new file mode 100644 index 000000000..942415b79 --- /dev/null +++ b/ports/zephyr/boards/nucleo_h743zi.conf @@ -0,0 +1,7 @@ +# disable console subsys to get REPL working +CONFIG_CONSOLE_SUBSYS=n + +# flash config +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y diff --git a/ports/zephyr/boards/nucleo_h743zi.overlay b/ports/zephyr/boards/nucleo_h743zi.overlay new file mode 100644 index 000000000..f5e6d4f92 --- /dev/null +++ b/ports/zephyr/boards/nucleo_h743zi.overlay @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 Thomas Popp + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* delete the already defined storage partition and create a new and bigger one */ +/delete-node/ &storage_partition; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + + /* storage slot: 1024 KB in Flash Memory Bank 2 */ + storage_partition: partition@100000 { + label = "storage"; + reg = <0x00100000 0x100000>; + }; + }; +}; diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index 4b29f41d1..aa8823392 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -37,12 +37,13 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/machine_i2c.h" +#include "modmachine.h" -STATIC const mp_obj_type_t machine_hard_i2c_type; +#if MICROPY_PY_MACHINE_I2C 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; @@ -52,6 +53,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 }, @@ -65,7 +68,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")); @@ -97,7 +100,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; @@ -127,11 +130,13 @@ 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, .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/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index c9c6a8c89..34c822c2f 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/machine_uart.c b/ports/zephyr/machine_uart.c new file mode 100644 index 000000000..23d7d5944 --- /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, uintptr_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 72078cf9d..107895bea 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -60,7 +60,10 @@ 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) }, + #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 766a9d7ea..957f2dd4f 100644 --- a/ports/zephyr/modmachine.h +++ b/ports/zephyr/modmachine.h @@ -4,12 +4,14 @@ #include "py/obj.h" 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); 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/modusocket.c b/ports/zephyr/modusocket.c index 6c900753c..f9fc96a2b 100644 --- a/ports/zephyr/modusocket.c +++ b/ports/zephyr/modusocket.c @@ -79,7 +79,8 @@ STATIC void parse_inet_addr(socket_obj_t *socket, mp_obj_t addr_in, struct socka mp_obj_t *addr_items; mp_obj_get_array_fixed_n(addr_in, 2, &addr_items); - sockaddr_in->sin_family = net_context_get_family((void *)socket->ctx); + void *context = zsock_get_context_object(socket->ctx); + sockaddr_in->sin_family = net_context_get_family(context); RAISE_ERRNO(net_addr_pton(sockaddr_in->sin_family, mp_obj_str_get_str(addr_items[0]), &sockaddr_in->sin_addr)); sockaddr_in->sin_port = htons(mp_obj_get_int(addr_items[1])); } @@ -119,8 +120,8 @@ STATIC void socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kin if (self->ctx == -1) { mp_printf(print, ""); } else { - struct net_context *ctx = (void *)self->ctx; - mp_printf(print, "", ctx, net_context_get_type(ctx)); + void *context = zsock_get_context_object(self->ctx); + mp_printf(print, "", self->ctx, net_context_get_type(context)); } } diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index 71b44d7df..52449ea38 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/modzsensor.c b/ports/zephyr/modzsensor.c index b01ce2693..01f05aacd 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/mpconfigport.h b/ports/zephyr/mpconfigport.h index f94ee7270..966f7f7e9 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) @@ -111,7 +110,7 @@ #define MICROPY_HW_MCU_NAME "unknown-cpu" #endif -#define MICROPY_MODULE_FROZEN_STR (1) +#define MICROPY_MODULE_FROZEN_STR (0) typedef int mp_int_t; // must be pointer size typedef unsigned mp_uint_t; // must be pointer size diff --git a/ports/zephyr/mphalport.h b/ports/zephyr/mphalport.h index 9ef213ddb..b3154b649 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")) diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 50cfa0050..c9c2e96da 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 @@ -65,3 +69,8 @@ CONFIG_NET_BUF_POOL_USAGE=y #CONFIG_NET_DEBUG_NET_BUF=y # Change to 4 for "DEBUG" level #CONFIG_SYS_LOG_NET_LEVEL=3 + +# MicroPython options +CONFIG_MICROPY_CONFIGFILE="mpconfigport.h" +CONFIG_MICROPY_VFS_FAT=y +CONFIG_MICROPY_VFS_LFS2=y diff --git a/ports/zephyr/uart_core.c b/ports/zephyr/uart_core.c index 63ecc8289..44bdeb5c2 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); } diff --git a/ports/zephyr/zephyr_storage.c b/ports/zephyr/zephyr_storage.c index 83f19a8fe..1c25b3277 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) { diff --git a/py/argcheck.c b/py/argcheck.c index c333ead05..bc6a8c4e5 100644 --- a/py/argcheck.c +++ b/py/argcheck.c @@ -28,7 +28,9 @@ #include #include "py/runtime.h" - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig) { // TODO maybe take the function name as an argument so we can print nicer error messages diff --git a/py/asmthumb.c b/py/asmthumb.c index bb10935fd..f7ac87fa0 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,10 +554,28 @@ 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 } } +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 17b694a74..17a0cca98 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); @@ -289,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 @@ -344,7 +387,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/bc.c b/py/bc.c index 34bc78fd3..aee35167f 100644 --- a/py/bc.c +++ b/py/bc.c @@ -40,7 +40,9 @@ #define DEBUG_printf(...) (void)0 #endif -#if !MICROPY_PERSISTENT_CODE + + +#if !MICROPY_PERSISTENT_CODE || MICROPY_EMIT_WASM mp_uint_t mp_decode_uint(const byte **ptr) { mp_uint_t unum = 0; @@ -74,6 +76,282 @@ const byte *mp_decode_uint_skip(const byte *ptr) { #endif +#if NO_NLR + +STATIC mp_obj_t fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + // generic message, used also for other argument issues + (void)f; + (void)expected; + (void)given; + mp_arg_error_terse_mismatch(); + return MP_OBJ_NULL; + #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL + (void)f; + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function takes %d positional arguments but %d were given", expected, given)); + #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "%q() takes %d positional arguments but %d were given", + mp_obj_fun_get_name(MP_OBJ_FROM_PTR(f)), expected, given)); + #endif +} + +#if DEBUG_PRINT +STATIC void dump_args(const mp_obj_t *a, size_t sz) { + DEBUG_printf("%p: ", a); + for (size_t i = 0; i < sz; i++) { + DEBUG_printf("%p ", a[i]); + } + DEBUG_printf("\n"); +} +#else +#define dump_args(...) (void)0 +#endif + +// On entry code_state should be allocated somewhere (stack/heap) and +// contain the following valid entries: +// - code_state->fun_bc should contain a pointer to the function object +// - code_state->ip should contain the offset in bytes from the pointer +// code_state->fun_bc->bytecode to the entry n_state (0 for bytecode, non-zero for native) +mp_obj_t mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // This function is pretty complicated. It's main aim is to be efficient in speed and RAM + // usage for the common case of positional only args. + + // get the function object that we want to set up (could be bytecode or native code) + mp_obj_fun_bc_t *self = code_state->fun_bc; + + // ip comes in as an offset into bytecode, so turn it into a true pointer + code_state->ip = self->bytecode + (size_t)code_state->ip; + + #if MICROPY_STACKLESS + code_state->prev = NULL; + #endif + + #if MICROPY_PY_SYS_SETTRACE + code_state->prev_state = NULL; + code_state->frame = NULL; + #endif + + // Get cached n_state (rather than decode it again) + size_t n_state = code_state->n_state; + + // Decode prelude + size_t n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args; + MP_BC_PRELUDE_SIG_DECODE_INTO(code_state->ip, n_state_unused, n_exc_stack_unused, scope_flags, n_pos_args, n_kwonly_args, n_def_pos_args); + (void)n_state_unused; + (void)n_exc_stack_unused; + + code_state->sp = &code_state->state[0] - 1; + code_state->exc_sp_idx = 0; + + // zero out the local stack to begin with + memset(code_state->state, 0, n_state * sizeof(*code_state->state)); + + const mp_obj_t *kwargs = args + n_args; + + // var_pos_kw_args points to the stack where the var-args tuple, and var-kw dict, should go (if they are needed) + mp_obj_t *var_pos_kw_args = &code_state->state[n_state - 1 - n_pos_args - n_kwonly_args]; + + // check positional arguments + + if (n_args > n_pos_args) { + // given more than enough arguments + if ((scope_flags & MP_SCOPE_FLAG_VARARGS) == 0) { + return fun_pos_args_mismatch(self, n_pos_args, n_args); + } + // put extra arguments in varargs tuple + *var_pos_kw_args-- = mp_obj_new_tuple(n_args - n_pos_args, args + n_pos_args); + n_args = n_pos_args; + } else { + if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) { + DEBUG_printf("passing empty tuple as *args\n"); + *var_pos_kw_args-- = mp_const_empty_tuple; + } + // Apply processing and check below only if we don't have kwargs, + // otherwise, kw handling code below has own extensive checks. + if (n_kw == 0 && (scope_flags & MP_SCOPE_FLAG_DEFKWARGS) == 0) { + if (n_args >= (size_t)(n_pos_args - n_def_pos_args)) { + // given enough arguments, but may need to use some default arguments + for (size_t i = n_args; i < n_pos_args; i++) { + code_state->state[n_state - 1 - i] = self->extra_args[i - (n_pos_args - n_def_pos_args)]; + } + } else { + return fun_pos_args_mismatch(self, n_pos_args - n_def_pos_args, n_args); + } + } + } + + // copy positional args into state + for (size_t i = 0; i < n_args; i++) { + code_state->state[n_state - 1 - i] = args[i]; + } + + // check keyword arguments + + if (n_kw != 0 || (scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { + DEBUG_printf("Initial args: "); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + + mp_obj_t dict = MP_OBJ_NULL; + if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { + dict = mp_obj_new_dict(n_kw); // TODO: better go conservative with 0? + *var_pos_kw_args = dict; + } + + // get pointer to arg_names array + const mp_obj_t *arg_names = (const mp_obj_t *)self->const_table; + + for (size_t i = 0; i < n_kw; i++) { + // the keys in kwargs are expected to be qstr objects + mp_obj_t wanted_arg_name = kwargs[2 * i]; + for (size_t j = 0; j < n_pos_args + n_kwonly_args; j++) { + if (wanted_arg_name == arg_names[j]) { + if (code_state->state[n_state - 1 - j] != MP_OBJ_NULL) { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function got multiple values for argument '%q'", MP_OBJ_QSTR_VALUE(wanted_arg_name))); + } + code_state->state[n_state - 1 - j] = kwargs[2 * i + 1]; + goto continue2; + } + } + // Didn't find name match with positional args + if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) == 0) { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + return mp_raise_TypeError_o("unexpected keyword argument"); + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "unexpected keyword argument '%q'", MP_OBJ_QSTR_VALUE(wanted_arg_name))); + } + } + mp_obj_dict_store(dict, kwargs[2 * i], kwargs[2 * i + 1]); + continue2:; + } + + DEBUG_printf("Args with kws flattened: "); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + + // fill in defaults for positional args + mp_obj_t *d = &code_state->state[n_state - n_pos_args]; + mp_obj_t *s = &self->extra_args[n_def_pos_args - 1]; + for (size_t i = n_def_pos_args; i > 0; i--, d++, s--) { + if (*d == MP_OBJ_NULL) { + *d = *s; + } + } + + DEBUG_printf("Args after filling default positional: "); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + + // Check that all mandatory positional args are specified + while (d < &code_state->state[n_state]) { + if (*d++ == MP_OBJ_NULL) { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function missing required positional argument #%d", &code_state->state[n_state] - d)); + } + } + + // Check that all mandatory keyword args are specified + // Fill in default kw args if we have them + for (size_t i = 0; i < n_kwonly_args; i++) { + if (code_state->state[n_state - 1 - n_pos_args - i] == MP_OBJ_NULL) { + mp_map_elem_t *elem = NULL; + if ((scope_flags & MP_SCOPE_FLAG_DEFKWARGS) != 0) { + elem = mp_map_lookup(&((mp_obj_dict_t *)MP_OBJ_TO_PTR(self->extra_args[n_def_pos_args]))->map, arg_names[n_pos_args + i], MP_MAP_LOOKUP); + } + if (elem != NULL) { + code_state->state[n_state - 1 - n_pos_args - i] = elem->value; + } else { + return mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "function missing required keyword argument '%q'", MP_OBJ_QSTR_VALUE(arg_names[n_pos_args + i]))); + } + } + } + + } else { + // no keyword arguments given + if (n_kwonly_args != 0) { + return mp_raise_TypeError_o("function missing keyword-only argument"); + } + if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) { + *var_pos_kw_args = mp_obj_new_dict(0); + } + } + + // read the size part of the prelude + const byte *ip = code_state->ip; + MP_BC_PRELUDE_SIZE_DECODE(ip); + + // jump over code info (source file and line-number mapping) + ip += n_info; + + // bytecode prelude: initialise closed over variables + for (; n_cell; --n_cell) { + size_t local_num = *ip++; + code_state->state[n_state - 1 - local_num] = + mp_obj_new_cell(code_state->state[n_state - 1 - local_num]); + } + + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + ip = MP_ALIGN(ip, sizeof(mp_uint_t)); + #endif + + // now that we skipped over the prelude, set the ip for the VM + code_state->ip = ip; + + DEBUG_printf("Calling: n_pos_args=%d, n_kwonly_args=%d\n", n_pos_args, n_kwonly_args); + dump_args(code_state->state + n_state - n_pos_args - n_kwonly_args, n_pos_args + n_kwonly_args); + dump_args(code_state->state, n_state); + + return MP_OBJ_SENTINEL; // success +} + +#if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE + +// The following table encodes the number of bytes that a specific opcode +// takes up. Some opcodes have an extra byte, defined by MP_BC_MASK_EXTRA_BYTE. +// There are 4 special opcodes that have an extra byte only when +// MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE is enabled (and they take a qstr): +// MP_BC_LOAD_NAME +// MP_BC_LOAD_GLOBAL +// MP_BC_LOAD_ATTR +// MP_BC_STORE_ATTR +uint mp_opcode_format(const byte *ip, size_t *opcode_size, bool count_var_uint) { + uint f = MP_BC_FORMAT(*ip); + const byte *ip_start = ip; + if (f == MP_BC_FORMAT_QSTR) { + if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE_DYNAMIC) { + if (*ip == MP_BC_LOAD_NAME + || *ip == MP_BC_LOAD_GLOBAL + || *ip == MP_BC_LOAD_ATTR + || *ip == MP_BC_STORE_ATTR) { + ip += 1; + } + } + ip += 3; + } else { + int extra_byte = (*ip & MP_BC_MASK_EXTRA_BYTE) == 0; + ip += 1; + if (f == MP_BC_FORMAT_VAR_UINT) { + if (count_var_uint) { + while ((*ip++ & 0x80) != 0) { + } + } + } else if (f == MP_BC_FORMAT_OFFSET) { + ip += 2; + } + ip += extra_byte; + } + *opcode_size = ip - ip_start; + return f; +} + +#endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE + +#else // NO_NLR + + STATIC NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE // generic message, used also for other argument issues @@ -341,3 +619,6 @@ uint mp_opcode_format(const byte *ip, size_t *opcode_size, bool count_var_uint) } #endif // MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE + +#endif // NO_NLR + diff --git a/py/bc.h b/py/bc.h index 16f314e19..8e83af234 100644 --- a/py/bc.h +++ b/py/bc.h @@ -225,7 +225,11 @@ 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); +#if NO_NLR +mp_obj_t mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); +#else void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); +#endif 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); diff --git a/py/bc0.h b/py/bc0.h index 842034ebf..fb6af3ecd 100644 --- a/py/bc0.h +++ b/py/bc0.h @@ -39,7 +39,7 @@ // Nibbles in magic number are: BB BB BB BB BB BO VV QU #define MP_BC_FORMAT(op) ((0x000003a4 >> (2 * ((op) >> 4))) & 3) -// Load, Store, Delete, Import, Make, Build, Unpack, Call, Jump, Exception, For, sTack, Return, Yield, Op +// Load, Store, Delete, Import, Make, Build, Unpack, Call, Jump, Exception, For, Stack, Return, Yield, Op #define MP_BC_BASE_RESERVED (0x00) // ---------------- #define MP_BC_BASE_QSTR_O (0x10) // LLLLLLSSSDDII--- #define MP_BC_BASE_VINT_E (0x20) // MMLLLLSSDDBBBBBB diff --git a/py/binary.c b/py/binary.c index d0f72ec23..d2f9de1bf 100644 --- a/py/binary.c +++ b/py/binary.c @@ -135,7 +135,11 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { } if (size == 0) { +#if NO_NLR + mp_raise_ValueError_o( mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("bad typecode"))); +#else mp_raise_ValueError(MP_ERROR_TEXT("bad typecode")); +#endif } if (palign != NULL) { @@ -321,7 +325,7 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p double f; } fp_dp; fp_dp.f = mp_obj_get_float_to_d(val_in); - if (BYTES_PER_WORD == 8) { + if (MP_BYTES_PER_OBJ_WORD == 8) { val = fp_dp.i64; } else { int be = struct_type == '>'; @@ -342,8 +346,8 @@ 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; + if (MP_BYTES_PER_OBJ_WORD < 8 && size > sizeof(val)) { + int c = (mp_int_t)val < 0 ? 0xff : 0x00; memset(p, c, size); if (struct_type == '>') { p += size - sizeof(val); diff --git a/py/builtinevex.c b/py/builtinevex.c index 800a20223..44c9fb314 100644 --- a/py/builtinevex.c +++ b/py/builtinevex.c @@ -58,6 +58,12 @@ STATIC mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj } // execute code +#if NO_NLR + mp_obj_t ret = mp_call_function_0(self->module_fun); + mp_globals_set(old_globals); + mp_locals_set(old_locals); + return ret; +#else nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t ret = mp_call_function_0(self->module_fun); @@ -71,6 +77,7 @@ STATIC mp_obj_t code_execute(mp_obj_code_t *self, mp_obj_dict_t *globals, mp_obj mp_locals_set(old_locals); nlr_jump(nlr.ret_val); } +#endif } STATIC mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { @@ -106,6 +113,11 @@ STATIC mp_obj_t mp_builtin_compile(size_t n_args, const mp_obj_t *args) { mp_obj_code_t *code = m_new_obj(mp_obj_code_t); code->base.type = &mp_type_code; code->module_fun = mp_parse_compile_execute(lex, parse_input_kind, NULL, NULL); +#if NO_NLR + if (code->module_fun == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_FROM_PTR(code); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_compile_obj, 3, 6, mp_builtin_compile); @@ -149,7 +161,11 @@ STATIC mp_obj_t eval_exec_helper(size_t n_args, const mp_obj_t *args, mp_parse_i } else { lex = mp_lexer_new_from_str_len(MP_QSTR__lt_string_gt_, bufinfo.buf, bufinfo.len, 0); } - +#if NO_NLR + if (lex == NULL) { + return MP_OBJ_NULL; + } +#endif return mp_parse_compile_execute(lex, parse_input_kind, globals, locals); } diff --git a/py/builtinimport.c b/py/builtinimport.c index bdc82e77c..84c0ba1b7 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include #include diff --git a/py/compile.c b/py/compile.c index d1a4d65c8..2822db70e 100644 --- a/py/compile.c +++ b/py/compile.c @@ -241,6 +241,11 @@ STATIC void compile_decrease_except_level(compiler_t *comp) { STATIC scope_t *scope_new_and_link(compiler_t *comp, scope_kind_t kind, mp_parse_node_t pn, uint emit_options) { scope_t *scope = scope_new(kind, pn, comp->source_file, emit_options); +#if NO_NLR + if (scope == NULL) { + return NULL; + } +#endif scope->parent = comp->scope_cur; scope->next = NULL; if (comp->scope_head == NULL) { @@ -1986,7 +1991,7 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { if (MP_PARSE_NODE_IS_NULL(pn_rhs)) { if (comp->is_repl && comp->scope_cur->kind == SCOPE_MODULE) { // for REPL, evaluate then print the expression - compile_load_id(comp, MP_QSTR___repl_print__); + compile_load_id(comp, MP_QSTR_displayhook); compile_node(comp, pns->nodes[0]); EMIT_ARG(call_function, 1, 0, 0); EMIT(pop_top); @@ -2001,99 +2006,104 @@ STATIC void compile_expr_stmt(compiler_t *comp, mp_parse_node_struct_t *pns) { EMIT(pop_top); // discard last result since this is a statement and leaves nothing on the stack } } - } else if (MP_PARSE_NODE_IS_STRUCT(pn_rhs)) { - mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn_rhs; - int kind = MP_PARSE_NODE_STRUCT_KIND(pns1); - if (kind == PN_annassign) { - // the annotation is in pns1->nodes[0] and is ignored - if (MP_PARSE_NODE_IS_NULL(pns1->nodes[1])) { - // an annotation of the form "x: y" - // inside a function this declares "x" as a local - if (comp->scope_cur->kind == SCOPE_FUNCTION) { - if (MP_PARSE_NODE_IS_ID(pns->nodes[0])) { - qstr lhs = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); - scope_find_or_add_id(comp->scope_cur, lhs, ID_INFO_KIND_LOCAL); - } + return; + } + if (!MP_PARSE_NODE_IS_STRUCT(pn_rhs)) + goto plain_assign; + + mp_parse_node_struct_t *pns1 = (mp_parse_node_struct_t *)pn_rhs; + int kind = MP_PARSE_NODE_STRUCT_KIND(pns1); + if (kind == PN_annassign) { + // the annotation is in pns1->nodes[0] and is ignored + if (MP_PARSE_NODE_IS_NULL(pns1->nodes[1])) { + // an annotation of the form "x: y" + // inside a function this declares "x" as a local + if (comp->scope_cur->kind == SCOPE_FUNCTION) { + if (MP_PARSE_NODE_IS_ID(pns->nodes[0])) { + qstr lhs = MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]); + scope_find_or_add_id(comp->scope_cur, lhs, ID_INFO_KIND_LOCAL); } - } else { - // an assigned annotation of the form "x: y = z" - pn_rhs = pns1->nodes[1]; - goto plain_assign; } - } else if (kind == PN_expr_stmt_augassign) { - c_assign(comp, pns->nodes[0], ASSIGN_AUG_LOAD); // lhs load for aug assign - compile_node(comp, pns1->nodes[1]); // rhs - assert(MP_PARSE_NODE_IS_TOKEN(pns1->nodes[0])); - mp_token_kind_t tok = MP_PARSE_NODE_LEAF_ARG(pns1->nodes[0]); - mp_binary_op_t op = MP_BINARY_OP_INPLACE_OR + (tok - MP_TOKEN_DEL_PIPE_EQUAL); - EMIT_ARG(binary_op, op); - c_assign(comp, pns->nodes[0], ASSIGN_AUG_STORE); // lhs store for aug assign - } else if (kind == PN_expr_stmt_assign_list) { - int rhs = MP_PARSE_NODE_STRUCT_NUM_NODES(pns1) - 1; - compile_node(comp, pns1->nodes[rhs]); // rhs - // following CPython, we store left-most first - if (rhs > 0) { + } else { + // an assigned annotation of the form "x: y = z" + pn_rhs = pns1->nodes[1]; + goto plain_assign; + } + return; + } else if (kind == PN_expr_stmt_augassign) { + c_assign(comp, pns->nodes[0], ASSIGN_AUG_LOAD); // lhs load for aug assign + compile_node(comp, pns1->nodes[1]); // rhs + assert(MP_PARSE_NODE_IS_TOKEN(pns1->nodes[0])); + mp_token_kind_t tok = (mp_token_kind_t)MP_PARSE_NODE_LEAF_ARG(pns1->nodes[0]); + mp_binary_op_t op = (mp_binary_op_t)(MP_BINARY_OP_INPLACE_OR + (tok - MP_TOKEN_DEL_PIPE_EQUAL)); + EMIT_ARG(binary_op, op); + c_assign(comp, pns->nodes[0], ASSIGN_AUG_STORE); // lhs store for aug assign + return; + } else if (kind == PN_expr_stmt_assign_list) { + int rhs = MP_PARSE_NODE_STRUCT_NUM_NODES(pns1) - 1; + compile_node(comp, pns1->nodes[rhs]); // rhs + // following CPython, we store left-most first + if (rhs > 0) { + EMIT(dup_top); + } + c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store + for (int i = 0; i < rhs; i++) { + if (i + 1 < rhs) { EMIT(dup_top); } - c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store - for (int i = 0; i < rhs; i++) { - if (i + 1 < rhs) { - EMIT(dup_top); - } - c_assign(comp, pns1->nodes[i], ASSIGN_STORE); // middle store + c_assign(comp, pns1->nodes[i], ASSIGN_STORE); // middle store + } + return; + } + +plain_assign: + #if MICROPY_COMP_DOUBLE_TUPLE_ASSIGN + if (MP_PARSE_NODE_IS_STRUCT_KIND(pn_rhs, PN_testlist_star_expr) + && MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_star_expr)) { + mp_parse_node_struct_t *pns0 = (mp_parse_node_struct_t *)pns->nodes[0]; + pns1 = (mp_parse_node_struct_t *)pn_rhs; + uint32_t n_pns0 = MP_PARSE_NODE_STRUCT_NUM_NODES(pns0); + // Can only optimise a tuple-to-tuple assignment when all of the following hold: + // - equal number of items in LHS and RHS tuples + // - 2 or 3 items in the tuples + // - there are no star expressions in the LHS tuple + if (n_pns0 == MP_PARSE_NODE_STRUCT_NUM_NODES(pns1) + && (n_pns0 == 2 + #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN + || n_pns0 == 3 + #endif + ) + && !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[0], PN_star_expr) + && !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[1], PN_star_expr) + #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN + && (n_pns0 == 2 || !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[2], PN_star_expr)) + #endif + ) { + // Optimisation for a, b = c, d or a, b, c = d, e, f + compile_node(comp, pns1->nodes[0]); // rhs + compile_node(comp, pns1->nodes[1]); // rhs + #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN + if (n_pns0 == 3) { + compile_node(comp, pns1->nodes[2]); // rhs + EMIT(rot_three); } - } else { - plain_assign: - #if MICROPY_COMP_DOUBLE_TUPLE_ASSIGN - if (MP_PARSE_NODE_IS_STRUCT_KIND(pn_rhs, PN_testlist_star_expr) - && MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_star_expr)) { - mp_parse_node_struct_t *pns0 = (mp_parse_node_struct_t *)pns->nodes[0]; - pns1 = (mp_parse_node_struct_t *)pn_rhs; - uint32_t n_pns0 = MP_PARSE_NODE_STRUCT_NUM_NODES(pns0); - // Can only optimise a tuple-to-tuple assignment when all of the following hold: - // - equal number of items in LHS and RHS tuples - // - 2 or 3 items in the tuples - // - there are no star expressions in the LHS tuple - if (n_pns0 == MP_PARSE_NODE_STRUCT_NUM_NODES(pns1) - && (n_pns0 == 2 - #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN - || n_pns0 == 3 - #endif - ) - && !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[0], PN_star_expr) - && !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[1], PN_star_expr) - #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN - && (n_pns0 == 2 || !MP_PARSE_NODE_IS_STRUCT_KIND(pns0->nodes[2], PN_star_expr)) - #endif - ) { - // Optimisation for a, b = c, d or a, b, c = d, e, f - compile_node(comp, pns1->nodes[0]); // rhs - compile_node(comp, pns1->nodes[1]); // rhs - #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN - if (n_pns0 == 3) { - compile_node(comp, pns1->nodes[2]); // rhs - EMIT(rot_three); - } - #endif - EMIT(rot_two); - c_assign(comp, pns0->nodes[0], ASSIGN_STORE); // lhs store - c_assign(comp, pns0->nodes[1], ASSIGN_STORE); // lhs store - #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN - if (n_pns0 == 3) { - c_assign(comp, pns0->nodes[2], ASSIGN_STORE); // lhs store - } - #endif - return; - } + #endif + EMIT(rot_two); + c_assign(comp, pns0->nodes[0], ASSIGN_STORE); // lhs store + c_assign(comp, pns0->nodes[1], ASSIGN_STORE); // lhs store + #if MICROPY_COMP_TRIPLE_TUPLE_ASSIGN + if (n_pns0 == 3) { + c_assign(comp, pns0->nodes[2], ASSIGN_STORE); // lhs store } #endif - - compile_node(comp, pn_rhs); // rhs - c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store + return; } - } else { - goto plain_assign; } + #endif + + compile_node(comp, pn_rhs); // rhs + c_assign(comp, pns->nodes[0], ASSIGN_STORE); // lhs store + } STATIC void compile_test_if_expr(compiler_t *comp, mp_parse_node_struct_t *pns) { @@ -2279,6 +2289,7 @@ STATIC void compile_atom_expr_normal(compiler_t *comp, mp_parse_node_struct_t *p size_t i = 0; // handle special super() call +#pragma message "FIXME: broken on super().__init__(self) when parent is dict" if (comp->scope_cur->kind == SCOPE_FUNCTION && MP_PARSE_NODE_IS_ID(pns->nodes[0]) && MP_PARSE_NODE_LEAF_ARG(pns->nodes[0]) == MP_QSTR_super @@ -3508,6 +3519,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f // create standard emitter; it's used at least for MP_PASS_SCOPE emit_t *emit_bc = emit_bc_new(); +#if NO_NLR + if (module_scope == NULL || emit_bc == NULL) { + return NULL; + } +#endif // compile pass 1 comp->emit = emit_bc; #if MICROPY_EMIT_NATIVE @@ -3523,6 +3539,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f { compile_scope(comp, s, MP_PASS_SCOPE); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif // 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]; @@ -3546,6 +3567,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f // set max number of labels now that it's calculated emit_bc_set_max_num_labels(emit_bc, max_num_labels); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif // compile pass 2 and 3 #if MICROPY_EMIT_NATIVE emit_t *emit_native = NULL; @@ -3571,7 +3597,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f compile_scope_inline_asm(comp, s, MP_PASS_CODE_SIZE); } #endif +#if NO_NLR + if (MP_STATE_THREAD(active_exception) == NULL && comp->compile_error == MP_OBJ_NULL) { +#else if (comp->compile_error == MP_OBJ_NULL) { +#endif compile_scope_inline_asm(comp, s, MP_PASS_EMIT); } } else @@ -3610,7 +3640,11 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f } // final pass: emit code +#if NO_NLR + if (MP_STATE_THREAD(active_exception) == NULL && comp->compile_error == MP_OBJ_NULL) { +#else if (comp->compile_error == MP_OBJ_NULL) { +#endif compile_scope(comp, s, MP_PASS_EMIT); } } @@ -3651,15 +3685,26 @@ mp_raw_code_t *mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_f } if (comp->compile_error != MP_OBJ_NULL) { - nlr_raise(comp->compile_error); + mp_raise_or_return_value(comp->compile_error, NULL); } else { return outer_raw_code; } } mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + // parser had an exception + return MP_OBJ_NULL; + } +#endif mp_raw_code_t *rc = mp_compile_to_raw_code(parse_tree, source_file, is_repl); // return function that executes the outer module +#if NO_NLR + if (rc == NULL) { + return MP_OBJ_NULL; + } +#endif return mp_make_function_from_raw_code(rc, MP_OBJ_NULL, MP_OBJ_NULL); } diff --git a/py/emitbc.c b/py/emitbc.c index c74b7fbc4..d7e8e05f0 100644 --- a/py/emitbc.c +++ b/py/emitbc.c @@ -36,7 +36,7 @@ #if MICROPY_ENABLE_COMPILER -#define BYTES_FOR_INT ((BYTES_PER_WORD * 8 + 6) / 7) +#define BYTES_FOR_INT ((MP_BYTES_PER_OBJ_WORD * 8 + 6) / 7) #define DUMMY_DATA_SIZE (BYTES_FOR_INT) struct _emit_t { diff --git a/py/emitcommon.c b/py/emitcommon.c index 791bf398a..19da9c942 100644 --- a/py/emitcommon.c +++ b/py/emitcommon.c @@ -33,6 +33,9 @@ void mp_emit_common_get_id_for_modification(scope_t *scope, qstr qst) { // name adding/lookup id_info_t *id = scope_find_or_add_id(scope, qst, ID_INFO_KIND_GLOBAL_IMPLICIT); + if (id == NULL) { + return; + } if (SCOPE_IS_FUNC_LIKE(scope->kind) && id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { // rebind as a local variable id->kind = ID_INFO_KIND_LOCAL; diff --git a/py/emitglue.c b/py/emitglue.c index 0ef708a3f..cbc001fed 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -52,6 +52,9 @@ mp_uint_t mp_verbose_flag = 0; mp_raw_code_t *mp_emit_glue_new_raw_code(void) { mp_raw_code_t *rc = m_new0(mp_raw_code_t, 1); + if (rc == NULL) { + return NULL; + } rc->kind = MP_CODE_RESERVED; #if MICROPY_PY_SYS_SETTRACE rc->line_of_definition = 0; @@ -85,7 +88,7 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #endif #ifdef DEBUG_PRINT - #if !MICROPY_DEBUG_PRINTERS + #if !MICROPY_DEBUG_PRINTERS && !MICROPY_PERSISTENT_CODE_SAVE const size_t len = 0; #endif DEBUG_printf("assign byte code: code=%p len=" UINT_FMT " flags=%x\n", code, len, (uint)scope_flags); diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index 2853da26c..1a35e25ad 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; } @@ -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') { @@ -701,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)) { @@ -725,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++) { diff --git a/py/emitinlinextensa.c b/py/emitinlinextensa.c index 6cc2e4d34..5dac2ae39 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; } diff --git a/py/emitnative.c b/py/emitnative.c index 2a657b696..20de361e2 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) @@ -841,7 +865,11 @@ STATIC vtype_kind_t load_reg_stack_imm(emit_t *emit, int reg_dest, const stack_i } else if (si->vtype == VTYPE_PTR_NONE) { emit_native_mov_reg_const(emit, reg_dest, MP_F_CONST_NONE_OBJ); } else { +#if NO_NLR + mp_raise_NotImplementedError_o(MP_ERROR_TEXT("conversion to object")); +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("conversion to object")); +#endif } return VTYPE_PYOBJ; } @@ -1104,13 +1132,16 @@ STATIC void emit_native_leave_exc_stack(emit_t *emit, bool start_of_handler) { } ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_EXC_HANDLER_PC(emit), REG_RET); } - +#if NO_NLR +#pragma message "TODO" +#else STATIC exc_stack_entry_t *emit_native_pop_exc_stack(emit_t *emit) { assert(emit->exc_stack_size > 0); exc_stack_entry_t *e = &emit->exc_stack[--emit->exc_stack_size]; assert(e->is_active == false); return e; } +#endif STATIC void emit_load_reg_with_ptr(emit_t *emit, int reg, mp_uint_t ptr, size_t table_off) { if (!emit->do_viper_types) { @@ -1166,6 +1197,9 @@ STATIC void emit_native_label_assign(emit_t *emit, mp_uint_t l) { } STATIC void emit_native_global_exc_entry(emit_t *emit) { +#if NO_NLR +#pragma message "TODO" +#else // Note: 4 labels are reserved for this function, starting at *emit->label_slot emit->exit_label = *emit->label_slot; @@ -1272,9 +1306,13 @@ STATIC void emit_native_global_exc_entry(emit_t *emit) { emit_call(emit, MP_F_NATIVE_RAISE); } } +#endif } STATIC void emit_native_global_exc_exit(emit_t *emit) { +#if NO_NLR +#pragma message "TODO" +#else // Label for end of function emit_native_label_assign(emit, emit->exit_label); @@ -1307,6 +1345,7 @@ STATIC void emit_native_global_exc_exit(emit_t *emit) { } ASM_EXIT(emit->as); +#endif } STATIC void emit_native_import_name(emit_t *emit, qstr qst) { @@ -2196,6 +2235,9 @@ STATIC void emit_native_with_cleanup(emit_t *emit, mp_uint_t label) { } STATIC void emit_native_end_finally(emit_t *emit) { +#if NO_NLR +#pragma message "TODO" +#else // logic: // exc = pop_stack // if exc == None: pass @@ -2219,6 +2261,7 @@ STATIC void emit_native_end_finally(emit_t *emit) { } emit_post(emit); +#endif } STATIC void emit_native_get_iter(emit_t *emit, bool use_stack) { @@ -2430,6 +2473,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, @@ -2449,6 +2493,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] = { @@ -2698,7 +2764,12 @@ STATIC void emit_native_call_function(emit_t *emit, mp_uint_t n_positional, mp_u break; default: // this can happen when casting a cast: int(int) +#if NO_NLR + mp_raise_NotImplementedError_o(MP_ERROR_TEXT("casting")); + return; +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("casting")); +#endif } } else { assert(vtype_fun == VTYPE_PYOBJ); @@ -2786,6 +2857,9 @@ STATIC void emit_native_return_value(emit_t *emit) { } STATIC void emit_native_raise_varargs(emit_t *emit, mp_uint_t n_args) { +#if NO_NLR +#pragma message "TODO" +#else (void)n_args; assert(n_args == 1); vtype_kind_t vtype_exc; @@ -2795,13 +2869,21 @@ STATIC void emit_native_raise_varargs(emit_t *emit, mp_uint_t n_args) { } // TODO probably make this 1 call to the runtime (which could even call convert, native_raise(obj, type)) emit_call(emit, MP_F_NATIVE_RAISE); +#endif } STATIC void emit_native_yield(emit_t *emit, int kind) { +#if NO_NLR +#pragma message "TODO" +#else // Note: 1 (yield) or 3 (yield from) labels are reserved for this function, starting at *emit->label_slot if (emit->do_viper_types) { +#if NO_NLR + mp_raise_NotImplementedError_o(MP_ERROR_TEXT("native yield")); +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("native yield")); +#endif } emit->scope->scope_flags |= MP_SCOPE_FLAG_GENERATOR; @@ -2878,6 +2960,7 @@ STATIC void emit_native_yield(emit_t *emit, int kind) { emit_native_adjust_stack_size(emit, 1); // ret_value emit_fold_stack_top(emit, REG_ARG_1); } +#endif } STATIC void emit_native_start_except_handler(emit_t *emit) { diff --git a/py/gc.c b/py/gc.c index 9c6336852..a928fce00 100644 --- a/py/gc.c +++ b/py/gc.c @@ -49,7 +49,7 @@ // detect untraced object still in use #define CLEAR_ON_SWEEP (0) -#define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / BYTES_PER_WORD) +#define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / MP_BYTES_PER_OBJ_WORD) #define BYTES_PER_BLOCK (MICROPY_BYTES_PER_GC_BLOCK) // ATB = allocation table byte @@ -118,9 +118,9 @@ void gc_init(void *start, void *end) { // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK) size_t total_byte_len = (byte *)end - (byte *)start; #if MICROPY_ENABLE_FINALISER - MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len * BITS_PER_BYTE / (BITS_PER_BYTE + BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK); + MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len * MP_BITS_PER_BYTE / (MP_BITS_PER_BYTE + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK); #else - MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len / (1 + BITS_PER_BYTE / 2 * BYTES_PER_BLOCK); + MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len / (1 + MP_BITS_PER_BYTE / 2 * BYTES_PER_BLOCK); #endif MP_STATE_MEM(gc_alloc_table_start) = (byte *)start; @@ -132,7 +132,7 @@ void gc_init(void *start, void *end) { size_t gc_pool_block_len = MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; MP_STATE_MEM(gc_pool_start) = (byte *)end - gc_pool_block_len * BYTES_PER_BLOCK; - MP_STATE_MEM(gc_pool_end) = end; + MP_STATE_MEM(gc_pool_end) = (byte *)end; #if MICROPY_ENABLE_FINALISER assert(MP_STATE_MEM(gc_pool_start) >= MP_STATE_MEM(gc_finaliser_table_start) + gc_finaliser_table_byte_len); @@ -294,11 +294,12 @@ 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 - // fall through to free the head + // fall through to free the head + MP_FALLTHROUGH case AT_TAIL: if (free_tail) { @@ -374,7 +375,7 @@ void gc_info(gc_info_t *info) { GC_ENTER(); info->total = MP_STATE_MEM(gc_pool_end) - MP_STATE_MEM(gc_pool_start); info->used = 0; - info->free = 0; + info->gc_free = 0; info->max_free = 0; info->num_1block = 0; info->num_2block = 0; @@ -384,7 +385,7 @@ void gc_info(gc_info_t *info) { size_t kind = ATB_GET_KIND(block); switch (kind) { case AT_FREE: - info->free += 1; + info->gc_free += 1; len_free += 1; len = 0; break; @@ -430,7 +431,7 @@ void gc_info(gc_info_t *info) { } info->used *= BYTES_PER_BLOCK; - info->free *= BYTES_PER_BLOCK; + info->gc_free *= BYTES_PER_BLOCK; GC_EXIT(); } @@ -800,7 +801,7 @@ void gc_dump_info(void) { gc_info_t info; gc_info(&info); mp_printf(&mp_plat_print, "GC: total: %u, used: %u, free: %u\n", - (uint)info.total, (uint)info.used, (uint)info.free); + (uint)info.total, (uint)info.used, (uint)info.gc_free); mp_printf(&mp_plat_print, " No. of 1-blocks: %u, 2-blocks: %u, max blk sz: %u, max free sz: %u\n", (uint)info.num_1block, (uint)info.num_2block, (uint)info.max_block, (uint)info.max_free); } diff --git a/py/gc.h b/py/gc.h index 4690d3937..2d8910121 100644 --- a/py/gc.h +++ b/py/gc.h @@ -26,10 +26,8 @@ #ifndef MICROPY_INCLUDED_PY_GC_H #define MICROPY_INCLUDED_PY_GC_H -#include - -#include "py/mpconfig.h" -#include "py/misc.h" +#include +#include void gc_init(void *start, void *end); @@ -60,7 +58,7 @@ void *gc_realloc(void *ptr, size_t n_bytes, bool allow_move); typedef struct _gc_info_t { size_t total; size_t used; - size_t free; + size_t gc_free; size_t max_free; size_t num_1block; size_t num_2block; diff --git a/py/grammar.h b/py/grammar.h index 285fbded2..0bdfba430 100644 --- a/py/grammar.h +++ b/py/grammar.h @@ -44,12 +44,12 @@ DEF_RULE_NC(generic_equal_test, and_ident(2), tok(DEL_EQUAL), rule(test)) // file_input: (NEWLINE | stmt)* ENDMARKER // eval_input: testlist NEWLINE* ENDMARKER -DEF_RULE_NC(single_input, or(3), tok(NEWLINE), rule(simple_stmt), rule(compound_stmt)) +DEF_RULE_NC(single_input, C_or(3), tok(NEWLINE), rule(simple_stmt), rule(compound_stmt)) DEF_RULE(file_input, c(generic_all_nodes), and_ident(1), opt_rule(file_input_2)) DEF_RULE(file_input_2, c(generic_all_nodes), one_or_more, rule(file_input_3)) -DEF_RULE_NC(file_input_3, or(2), tok(NEWLINE), rule(stmt)) +DEF_RULE_NC(file_input_3, C_or(2), tok(NEWLINE), rule(stmt)) DEF_RULE_NC(eval_input, and_ident(2), rule(testlist), opt_rule(eval_input_2)) -DEF_RULE_NC(eval_input_2, and(1), tok(NEWLINE)) +DEF_RULE_NC(eval_input_2, C_and(1), tok(NEWLINE)) // decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE // decorators: decorator+ @@ -62,35 +62,35 @@ DEF_RULE_NC(eval_input_2, and(1), tok(NEWLINE)) // varargslist: vfpdef ['=' test] (',' vfpdef ['=' test])* [',' ['*' [vfpdef] (',' vfpdef ['=' test])* [',' '**' vfpdef] | '**' vfpdef]] | '*' [vfpdef] (',' vfpdef ['=' test])* [',' '**' vfpdef] | '**' vfpdef // vfpdef: NAME -DEF_RULE_NC(decorator, and(4), tok(OP_AT), rule(dotted_name), opt_rule(trailer_paren), tok(NEWLINE)) +DEF_RULE_NC(decorator, C_and(4), tok(OP_AT), rule(dotted_name), opt_rule(trailer_paren), tok(NEWLINE)) DEF_RULE_NC(decorators, one_or_more, rule(decorator)) DEF_RULE(decorated, c(decorated), and_ident(2), rule(decorators), rule(decorated_body)) #if MICROPY_PY_ASYNC_AWAIT -DEF_RULE_NC(decorated_body, or(3), rule(classdef), rule(funcdef), rule(async_funcdef)) -DEF_RULE_NC(async_funcdef, and(2), tok(KW_ASYNC), rule(funcdef)) +DEF_RULE_NC(decorated_body, C_or(3), rule(classdef), rule(funcdef), rule(async_funcdef)) +DEF_RULE_NC(async_funcdef, C_and(2), tok(KW_ASYNC), rule(funcdef)) #else -DEF_RULE_NC(decorated_body, or(2), rule(classdef), rule(funcdef)) +DEF_RULE_NC(decorated_body, C_or(2), rule(classdef), rule(funcdef)) #endif DEF_RULE(funcdef, c(funcdef), and_blank(8), tok(KW_DEF), tok(NAME), tok(DEL_PAREN_OPEN), opt_rule(typedargslist), tok(DEL_PAREN_CLOSE), opt_rule(funcdefrettype), tok(DEL_COLON), rule(suite)) DEF_RULE_NC(funcdefrettype, and_ident(2), tok(DEL_MINUS_MORE), rule(test)) // note: typedargslist lets through more than is allowed, compiler does further checks DEF_RULE_NC(typedargslist, list_with_end, rule(typedargslist_item), tok(DEL_COMMA)) -DEF_RULE_NC(typedargslist_item, or(3), rule(typedargslist_name), rule(typedargslist_star), rule(typedargslist_dbl_star)) +DEF_RULE_NC(typedargslist_item, C_or(3), rule(typedargslist_name), rule(typedargslist_star), rule(typedargslist_dbl_star)) DEF_RULE_NC(typedargslist_name, and_ident(3), tok(NAME), opt_rule(generic_colon_test), opt_rule(generic_equal_test)) -DEF_RULE_NC(typedargslist_star, and(2), tok(OP_STAR), opt_rule(tfpdef)) -DEF_RULE_NC(typedargslist_dbl_star, and(3), tok(OP_DBL_STAR), tok(NAME), opt_rule(generic_colon_test)) -DEF_RULE_NC(tfpdef, and(2), tok(NAME), opt_rule(generic_colon_test)) +DEF_RULE_NC(typedargslist_star, C_and(2), tok(OP_STAR), opt_rule(tfpdef)) +DEF_RULE_NC(typedargslist_dbl_star, C_and(3), tok(OP_DBL_STAR), tok(NAME), opt_rule(generic_colon_test)) +DEF_RULE_NC(tfpdef, C_and(2), tok(NAME), opt_rule(generic_colon_test)) // note: varargslist lets through more than is allowed, compiler does further checks DEF_RULE_NC(varargslist, list_with_end, rule(varargslist_item), tok(DEL_COMMA)) -DEF_RULE_NC(varargslist_item, or(3), rule(varargslist_name), rule(varargslist_star), rule(varargslist_dbl_star)) +DEF_RULE_NC(varargslist_item, C_or(3), rule(varargslist_name), rule(varargslist_star), rule(varargslist_dbl_star)) DEF_RULE_NC(varargslist_name, and_ident(2), tok(NAME), opt_rule(generic_equal_test)) -DEF_RULE_NC(varargslist_star, and(2), tok(OP_STAR), opt_rule(vfpdef)) -DEF_RULE_NC(varargslist_dbl_star, and(2), tok(OP_DBL_STAR), tok(NAME)) +DEF_RULE_NC(varargslist_star, C_and(2), tok(OP_STAR), opt_rule(vfpdef)) +DEF_RULE_NC(varargslist_dbl_star, C_and(2), tok(OP_DBL_STAR), tok(NAME)) DEF_RULE_NC(vfpdef, and_ident(1), tok(NAME)) // stmt: compound_stmt | simple_stmt -DEF_RULE_NC(stmt, or(2), rule(compound_stmt), rule(simple_stmt)) +DEF_RULE_NC(stmt, C_or(2), rule(compound_stmt), rule(simple_stmt)) // simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE @@ -104,17 +104,17 @@ DEF_RULE(simple_stmt_2, c(generic_all_nodes), list_with_end, rule(small_stmt), t // augassign: '+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=' // # For normal and annotated assignments, additional restrictions enforced by the interpreter -DEF_RULE_NC(small_stmt, or(8), rule(del_stmt), rule(pass_stmt), rule(flow_stmt), rule(import_stmt), rule(global_stmt), rule(nonlocal_stmt), rule(assert_stmt), rule(expr_stmt)) -DEF_RULE(expr_stmt, c(expr_stmt), and(2), rule(testlist_star_expr), opt_rule(expr_stmt_2)) -DEF_RULE_NC(expr_stmt_2, or(3), rule(annassign), rule(expr_stmt_augassign), rule(expr_stmt_assign_list)) +DEF_RULE_NC(small_stmt, C_or(8), rule(del_stmt), rule(pass_stmt), rule(flow_stmt), rule(import_stmt), rule(global_stmt), rule(nonlocal_stmt), rule(assert_stmt), rule(expr_stmt)) +DEF_RULE(expr_stmt, c(expr_stmt), C_and(2), rule(testlist_star_expr), opt_rule(expr_stmt_2)) +DEF_RULE_NC(expr_stmt_2, C_or(3), rule(annassign), rule(expr_stmt_augassign), rule(expr_stmt_assign_list)) DEF_RULE_NC(expr_stmt_augassign, and_ident(2), rule(augassign), rule(expr_stmt_6)) DEF_RULE_NC(expr_stmt_assign_list, one_or_more, rule(expr_stmt_assign)) DEF_RULE_NC(expr_stmt_assign, and_ident(2), tok(DEL_EQUAL), rule(expr_stmt_6)) -DEF_RULE_NC(expr_stmt_6, or(2), rule(yield_expr), rule(testlist_star_expr)) +DEF_RULE_NC(expr_stmt_6, C_or(2), rule(yield_expr), rule(testlist_star_expr)) DEF_RULE(testlist_star_expr, c(generic_tuple), list_with_end, rule(testlist_star_expr_2), tok(DEL_COMMA)) -DEF_RULE_NC(testlist_star_expr_2, or(2), rule(star_expr), rule(test)) -DEF_RULE_NC(annassign, and(3), tok(DEL_COLON), rule(test), opt_rule(expr_stmt_assign)) -DEF_RULE_NC(augassign, or(13), tok(DEL_PLUS_EQUAL), tok(DEL_MINUS_EQUAL), tok(DEL_STAR_EQUAL), tok(DEL_AT_EQUAL), tok(DEL_SLASH_EQUAL), tok(DEL_PERCENT_EQUAL), tok(DEL_AMPERSAND_EQUAL), tok(DEL_PIPE_EQUAL), tok(DEL_CARET_EQUAL), tok(DEL_DBL_LESS_EQUAL), tok(DEL_DBL_MORE_EQUAL), tok(DEL_DBL_STAR_EQUAL), tok(DEL_DBL_SLASH_EQUAL)) +DEF_RULE_NC(testlist_star_expr_2, C_or(2), rule(star_expr), rule(test)) +DEF_RULE_NC(annassign, C_and(3), tok(DEL_COLON), rule(test), opt_rule(expr_stmt_assign)) +DEF_RULE_NC(augassign, C_or(13), tok(DEL_PLUS_EQUAL), tok(DEL_MINUS_EQUAL), tok(DEL_STAR_EQUAL), tok(DEL_AT_EQUAL), tok(DEL_SLASH_EQUAL), tok(DEL_PERCENT_EQUAL), tok(DEL_AMPERSAND_EQUAL), tok(DEL_PIPE_EQUAL), tok(DEL_CARET_EQUAL), tok(DEL_DBL_LESS_EQUAL), tok(DEL_DBL_MORE_EQUAL), tok(DEL_DBL_STAR_EQUAL), tok(DEL_DBL_SLASH_EQUAL)) // del_stmt: 'del' exprlist // pass_stmt: 'pass' @@ -125,14 +125,14 @@ DEF_RULE_NC(augassign, or(13), tok(DEL_PLUS_EQUAL), tok(DEL_MINUS_EQUAL), tok(DE // yield_stmt: yield_expr // raise_stmt: 'raise' [test ['from' test]] -DEF_RULE(del_stmt, c(del_stmt), and(2), tok(KW_DEL), rule(exprlist)) -DEF_RULE(pass_stmt, c(generic_all_nodes), and(1), tok(KW_PASS)) -DEF_RULE_NC(flow_stmt, or(5), rule(break_stmt), rule(continue_stmt), rule(return_stmt), rule(raise_stmt), rule(yield_stmt)) -DEF_RULE(break_stmt, c(break_cont_stmt), and(1), tok(KW_BREAK)) -DEF_RULE(continue_stmt, c(break_cont_stmt), and(1), tok(KW_CONTINUE)) -DEF_RULE(return_stmt, c(return_stmt), and(2), tok(KW_RETURN), opt_rule(testlist)) -DEF_RULE(yield_stmt, c(yield_stmt), and(1), rule(yield_expr)) -DEF_RULE(raise_stmt, c(raise_stmt), and(2), tok(KW_RAISE), opt_rule(raise_stmt_arg)) +DEF_RULE(del_stmt, c(del_stmt), C_and(2), tok(KW_DEL), rule(exprlist)) +DEF_RULE(pass_stmt, c(generic_all_nodes), C_and(1), tok(KW_PASS)) +DEF_RULE_NC(flow_stmt, C_or(5), rule(break_stmt), rule(continue_stmt), rule(return_stmt), rule(raise_stmt), rule(yield_stmt)) +DEF_RULE(break_stmt, c(break_cont_stmt), C_and(1), tok(KW_BREAK)) +DEF_RULE(continue_stmt, c(break_cont_stmt), C_and(1), tok(KW_CONTINUE)) +DEF_RULE(return_stmt, c(return_stmt), C_and(2), tok(KW_RETURN), opt_rule(testlist)) +DEF_RULE(yield_stmt, c(yield_stmt), C_and(1), rule(yield_expr)) +DEF_RULE(raise_stmt, c(raise_stmt), C_and(2), tok(KW_RAISE), opt_rule(raise_stmt_arg)) DEF_RULE_NC(raise_stmt_arg, and_ident(2), rule(test), opt_rule(raise_stmt_from)) DEF_RULE_NC(raise_stmt_from, and_ident(2), tok(KW_FROM), rule(test)) @@ -148,25 +148,25 @@ DEF_RULE_NC(raise_stmt_from, and_ident(2), tok(KW_FROM), rule(test)) // nonlocal_stmt: 'nonlocal' NAME (',' NAME)* // assert_stmt: 'assert' test [',' test] -DEF_RULE_NC(import_stmt, or(2), rule(import_name), rule(import_from)) -DEF_RULE(import_name, c(import_name), and(2), tok(KW_IMPORT), rule(dotted_as_names)) -DEF_RULE(import_from, c(import_from), and(4), tok(KW_FROM), rule(import_from_2), tok(KW_IMPORT), rule(import_from_3)) -DEF_RULE_NC(import_from_2, or(2), rule(dotted_name), rule(import_from_2b)) +DEF_RULE_NC(import_stmt, C_or(2), rule(import_name), rule(import_from)) +DEF_RULE(import_name, c(import_name), C_and(2), tok(KW_IMPORT), rule(dotted_as_names)) +DEF_RULE(import_from, c(import_from), C_and(4), tok(KW_FROM), rule(import_from_2), tok(KW_IMPORT), rule(import_from_3)) +DEF_RULE_NC(import_from_2, C_or(2), rule(dotted_name), rule(import_from_2b)) DEF_RULE_NC(import_from_2b, and_ident(2), rule(one_or_more_period_or_ellipsis), opt_rule(dotted_name)) -DEF_RULE_NC(import_from_3, or(3), tok(OP_STAR), rule(import_as_names_paren), rule(import_as_names)) +DEF_RULE_NC(import_from_3, C_or(3), tok(OP_STAR), rule(import_as_names_paren), rule(import_as_names)) DEF_RULE_NC(import_as_names_paren, and_ident(3), tok(DEL_PAREN_OPEN), rule(import_as_names), tok(DEL_PAREN_CLOSE)) DEF_RULE_NC(one_or_more_period_or_ellipsis, one_or_more, rule(period_or_ellipsis)) -DEF_RULE_NC(period_or_ellipsis, or(2), tok(DEL_PERIOD), tok(ELLIPSIS)) -DEF_RULE_NC(import_as_name, and(2), tok(NAME), opt_rule(as_name)) +DEF_RULE_NC(period_or_ellipsis, C_or(2), tok(DEL_PERIOD), tok(ELLIPSIS)) +DEF_RULE_NC(import_as_name, C_and(2), tok(NAME), opt_rule(as_name)) DEF_RULE_NC(dotted_as_name, and_ident(2), rule(dotted_name), opt_rule(as_name)) DEF_RULE_NC(as_name, and_ident(2), tok(KW_AS), tok(NAME)) DEF_RULE_NC(import_as_names, list_with_end, rule(import_as_name), tok(DEL_COMMA)) DEF_RULE_NC(dotted_as_names, list, rule(dotted_as_name), tok(DEL_COMMA)) DEF_RULE_NC(dotted_name, list, tok(NAME), tok(DEL_PERIOD)) -DEF_RULE(global_stmt, c(global_nonlocal_stmt), and(2), tok(KW_GLOBAL), rule(name_list)) -DEF_RULE(nonlocal_stmt, c(global_nonlocal_stmt), and(2), tok(KW_NONLOCAL), rule(name_list)) +DEF_RULE(global_stmt, c(global_nonlocal_stmt), C_and(2), tok(KW_GLOBAL), rule(name_list)) +DEF_RULE(nonlocal_stmt, c(global_nonlocal_stmt), C_and(2), tok(KW_NONLOCAL), rule(name_list)) DEF_RULE_NC(name_list, list, tok(NAME), tok(DEL_COMMA)) -DEF_RULE(assert_stmt, c(assert_stmt), and(3), tok(KW_ASSERT), rule(test), opt_rule(assert_stmt_extra)) +DEF_RULE(assert_stmt, c(assert_stmt), C_and(3), tok(KW_ASSERT), rule(test), opt_rule(assert_stmt_extra)) DEF_RULE_NC(assert_stmt_extra, and_ident(2), tok(DEL_COMMA), rule(test)) // compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt @@ -182,30 +182,30 @@ DEF_RULE_NC(assert_stmt_extra, and_ident(2), tok(DEL_COMMA), rule(test)) // async_stmt: 'async' (funcdef | with_stmt | for_stmt) #if MICROPY_PY_ASYNC_AWAIT -DEF_RULE_NC(compound_stmt, or(9), rule(if_stmt), rule(while_stmt), rule(for_stmt), rule(try_stmt), rule(with_stmt), rule(funcdef), rule(classdef), rule(decorated), rule(async_stmt)) -DEF_RULE(async_stmt, c(async_stmt), and(2), tok(KW_ASYNC), rule(async_stmt_2)) -DEF_RULE_NC(async_stmt_2, or(3), rule(funcdef), rule(with_stmt), rule(for_stmt)) +DEF_RULE_NC(compound_stmt, C_or(9), rule(if_stmt), rule(while_stmt), rule(for_stmt), rule(try_stmt), rule(with_stmt), rule(funcdef), rule(classdef), rule(decorated), rule(async_stmt)) +DEF_RULE(async_stmt, c(async_stmt), C_and(2), tok(KW_ASYNC), rule(async_stmt_2)) +DEF_RULE_NC(async_stmt_2, C_or(3), rule(funcdef), rule(with_stmt), rule(for_stmt)) #else -DEF_RULE_NC(compound_stmt, or(8), rule(if_stmt), rule(while_stmt), rule(for_stmt), rule(try_stmt), rule(with_stmt), rule(funcdef), rule(classdef), rule(decorated)) +DEF_RULE_NC(compound_stmt, C_or(8), rule(if_stmt), rule(while_stmt), rule(for_stmt), rule(try_stmt), rule(with_stmt), rule(funcdef), rule(classdef), rule(decorated)) #endif -DEF_RULE(if_stmt, c(if_stmt), and(6), tok(KW_IF), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(if_stmt_elif_list), opt_rule(else_stmt)) +DEF_RULE(if_stmt, c(if_stmt), C_and(6), tok(KW_IF), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(if_stmt_elif_list), opt_rule(else_stmt)) DEF_RULE_NC(if_stmt_elif_list, one_or_more, rule(if_stmt_elif)) -DEF_RULE_NC(if_stmt_elif, and(4), tok(KW_ELIF), rule(namedexpr_test), tok(DEL_COLON), rule(suite)) -DEF_RULE(while_stmt, c(while_stmt), and(5), tok(KW_WHILE), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) -DEF_RULE(for_stmt, c(for_stmt), and(7), tok(KW_FOR), rule(exprlist), tok(KW_IN), rule(testlist), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) -DEF_RULE(try_stmt, c(try_stmt), and(4), tok(KW_TRY), tok(DEL_COLON), rule(suite), rule(try_stmt_2)) -DEF_RULE_NC(try_stmt_2, or(2), rule(try_stmt_except_and_more), rule(try_stmt_finally)) +DEF_RULE_NC(if_stmt_elif, C_and(4), tok(KW_ELIF), rule(namedexpr_test), tok(DEL_COLON), rule(suite)) +DEF_RULE(while_stmt, c(while_stmt), C_and(5), tok(KW_WHILE), rule(namedexpr_test), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) +DEF_RULE(for_stmt, c(for_stmt), C_and(7), tok(KW_FOR), rule(exprlist), tok(KW_IN), rule(testlist), tok(DEL_COLON), rule(suite), opt_rule(else_stmt)) +DEF_RULE(try_stmt, c(try_stmt), C_and(4), tok(KW_TRY), tok(DEL_COLON), rule(suite), rule(try_stmt_2)) +DEF_RULE_NC(try_stmt_2, C_or(2), rule(try_stmt_except_and_more), rule(try_stmt_finally)) DEF_RULE_NC(try_stmt_except_and_more, and_ident(3), rule(try_stmt_except_list), opt_rule(else_stmt), opt_rule(try_stmt_finally)) -DEF_RULE_NC(try_stmt_except, and(4), tok(KW_EXCEPT), opt_rule(try_stmt_as_name), tok(DEL_COLON), rule(suite)) +DEF_RULE_NC(try_stmt_except, C_and(4), tok(KW_EXCEPT), opt_rule(try_stmt_as_name), tok(DEL_COLON), rule(suite)) DEF_RULE_NC(try_stmt_as_name, and_ident(2), rule(test), opt_rule(as_name)) DEF_RULE_NC(try_stmt_except_list, one_or_more, rule(try_stmt_except)) -DEF_RULE_NC(try_stmt_finally, and(3), tok(KW_FINALLY), tok(DEL_COLON), rule(suite)) +DEF_RULE_NC(try_stmt_finally, C_and(3), tok(KW_FINALLY), tok(DEL_COLON), rule(suite)) DEF_RULE_NC(else_stmt, and_ident(3), tok(KW_ELSE), tok(DEL_COLON), rule(suite)) -DEF_RULE(with_stmt, c(with_stmt), and(4), tok(KW_WITH), rule(with_stmt_list), tok(DEL_COLON), rule(suite)) +DEF_RULE(with_stmt, c(with_stmt), C_and(4), tok(KW_WITH), rule(with_stmt_list), tok(DEL_COLON), rule(suite)) DEF_RULE_NC(with_stmt_list, list, rule(with_item), tok(DEL_COMMA)) DEF_RULE_NC(with_item, and_ident(2), rule(test), opt_rule(with_item_as)) DEF_RULE_NC(with_item_as, and_ident(2), tok(KW_AS), rule(expr)) -DEF_RULE_NC(suite, or(2), rule(suite_block), rule(simple_stmt)) +DEF_RULE_NC(suite, C_or(2), rule(suite_block), rule(simple_stmt)) DEF_RULE_NC(suite_block, and_ident(4), tok(NEWLINE), tok(INDENT), rule(suite_block_stmts), tok(DEDENT)) DEF_RULE(suite_block_stmts, c(generic_all_nodes), one_or_more, rule(stmt)) @@ -218,12 +218,12 @@ DEF_RULE(suite_block_stmts, c(generic_all_nodes), one_or_more, rule(stmt)) DEF_RULE(namedexpr_test, c(namedexpr), and_ident(2), rule(test), opt_rule(namedexpr_test_2)) DEF_RULE_NC(namedexpr_test_2, and_ident(2), tok(OP_ASSIGN), rule(test)) #else -DEF_RULE_NC(namedexpr_test, or(1), rule(test)) +DEF_RULE_NC(namedexpr_test, C_or(1), rule(test)) #endif -DEF_RULE_NC(test, or(2), rule(lambdef), rule(test_if_expr)) +DEF_RULE_NC(test, C_or(2), rule(lambdef), rule(test_if_expr)) DEF_RULE(test_if_expr, c(test_if_expr), and_ident(2), rule(or_test), opt_rule(test_if_else)) -DEF_RULE_NC(test_if_else, and(4), tok(KW_IF), rule(or_test), tok(KW_ELSE), rule(test)) -DEF_RULE_NC(test_nocond, or(2), rule(lambdef_nocond), rule(or_test)) +DEF_RULE_NC(test_if_else, C_and(4), tok(KW_IF), rule(or_test), tok(KW_ELSE), rule(test)) +DEF_RULE_NC(test_nocond, C_or(2), rule(lambdef_nocond), rule(or_test)) DEF_RULE(lambdef, c(lambdef), and_blank(4), tok(KW_LAMBDA), opt_rule(varargslist), tok(DEL_COLON), rule(test)) DEF_RULE(lambdef_nocond, c(lambdef), and_blank(4), tok(KW_LAMBDA), opt_rule(varargslist), tok(DEL_COLON), rule(test_nocond)) @@ -245,32 +245,32 @@ DEF_RULE(lambdef_nocond, c(lambdef), and_blank(4), tok(KW_LAMBDA), opt_rule(vara DEF_RULE(or_test, c(or_and_test), list, rule(and_test), tok(KW_OR)) DEF_RULE(and_test, c(or_and_test), list, rule(not_test), tok(KW_AND)) -DEF_RULE_NC(not_test, or(2), rule(not_test_2), rule(comparison)) -DEF_RULE(not_test_2, c(not_test_2), and(2), tok(KW_NOT), rule(not_test)) +DEF_RULE_NC(not_test, C_or(2), rule(not_test_2), rule(comparison)) +DEF_RULE(not_test_2, c(not_test_2), C_and(2), tok(KW_NOT), rule(not_test)) DEF_RULE(comparison, c(comparison), list, rule(expr), rule(comp_op)) -DEF_RULE_NC(comp_op, or(9), tok(OP_LESS), tok(OP_MORE), tok(OP_DBL_EQUAL), tok(OP_LESS_EQUAL), tok(OP_MORE_EQUAL), tok(OP_NOT_EQUAL), tok(KW_IN), rule(comp_op_not_in), rule(comp_op_is)) -DEF_RULE_NC(comp_op_not_in, and(2), tok(KW_NOT), tok(KW_IN)) -DEF_RULE_NC(comp_op_is, and(2), tok(KW_IS), opt_rule(comp_op_is_not)) -DEF_RULE_NC(comp_op_is_not, and(1), tok(KW_NOT)) -DEF_RULE(star_expr, c(star_expr), and(2), tok(OP_STAR), rule(expr)) +DEF_RULE_NC(comp_op, C_or(9), tok(OP_LESS), tok(OP_MORE), tok(OP_DBL_EQUAL), tok(OP_LESS_EQUAL), tok(OP_MORE_EQUAL), tok(OP_NOT_EQUAL), tok(KW_IN), rule(comp_op_not_in), rule(comp_op_is)) +DEF_RULE_NC(comp_op_not_in, C_and(2), tok(KW_NOT), tok(KW_IN)) +DEF_RULE_NC(comp_op_is, C_and(2), tok(KW_IS), opt_rule(comp_op_is_not)) +DEF_RULE_NC(comp_op_is_not, C_and(1), tok(KW_NOT)) +DEF_RULE(star_expr, c(star_expr), C_and(2), tok(OP_STAR), rule(expr)) DEF_RULE(expr, c(binary_op), list, rule(xor_expr), tok(OP_PIPE)) DEF_RULE(xor_expr, c(binary_op), list, rule(and_expr), tok(OP_CARET)) DEF_RULE(and_expr, c(binary_op), list, rule(shift_expr), tok(OP_AMPERSAND)) DEF_RULE(shift_expr, c(term), list, rule(arith_expr), rule(shift_op)) -DEF_RULE_NC(shift_op, or(2), tok(OP_DBL_LESS), tok(OP_DBL_MORE)) +DEF_RULE_NC(shift_op, C_or(2), tok(OP_DBL_LESS), tok(OP_DBL_MORE)) DEF_RULE(arith_expr, c(term), list, rule(term), rule(arith_op)) -DEF_RULE_NC(arith_op, or(2), tok(OP_PLUS), tok(OP_MINUS)) +DEF_RULE_NC(arith_op, C_or(2), tok(OP_PLUS), tok(OP_MINUS)) DEF_RULE(term, c(term), list, rule(factor), rule(term_op)) -DEF_RULE_NC(term_op, or(5), tok(OP_STAR), tok(OP_AT), tok(OP_SLASH), tok(OP_PERCENT), tok(OP_DBL_SLASH)) -DEF_RULE_NC(factor, or(2), rule(factor_2), rule(power)) +DEF_RULE_NC(term_op, C_or(5), tok(OP_STAR), tok(OP_AT), tok(OP_SLASH), tok(OP_PERCENT), tok(OP_DBL_SLASH)) +DEF_RULE_NC(factor, C_or(2), rule(factor_2), rule(power)) DEF_RULE(factor_2, c(factor_2), and_ident(2), rule(factor_op), rule(factor)) -DEF_RULE_NC(factor_op, or(3), tok(OP_PLUS), tok(OP_MINUS), tok(OP_TILDE)) +DEF_RULE_NC(factor_op, C_or(3), tok(OP_PLUS), tok(OP_MINUS), tok(OP_TILDE)) DEF_RULE(power, c(power), and_ident(2), rule(atom_expr), opt_rule(power_dbl_star)) #if MICROPY_PY_ASYNC_AWAIT -DEF_RULE_NC(atom_expr, or(2), rule(atom_expr_await), rule(atom_expr_normal)) -DEF_RULE(atom_expr_await, c(atom_expr_await), and(3), tok(KW_AWAIT), rule(atom), opt_rule(atom_expr_trailers)) +DEF_RULE_NC(atom_expr, C_or(2), rule(atom_expr_await), rule(atom_expr_normal)) +DEF_RULE(atom_expr_await, c(atom_expr_await), C_and(3), tok(KW_AWAIT), rule(atom), opt_rule(atom_expr_trailers)) #else -DEF_RULE_NC(atom_expr, or(1), rule(atom_expr_normal)) +DEF_RULE_NC(atom_expr, C_or(1), rule(atom_expr_normal)) #endif DEF_RULE(atom_expr_normal, c(atom_expr_normal), and_ident(2), rule(atom), opt_rule(atom_expr_trailers)) DEF_RULE_NC(atom_expr_trailers, one_or_more, rule(trailer)) @@ -280,20 +280,20 @@ DEF_RULE_NC(power_dbl_star, and_ident(2), tok(OP_DBL_STAR), rule(factor)) // testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) // trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME -DEF_RULE_NC(atom, or(12), tok(NAME), tok(INTEGER), tok(FLOAT_OR_IMAG), tok(STRING), tok(BYTES), tok(ELLIPSIS), tok(KW_NONE), tok(KW_TRUE), tok(KW_FALSE), rule(atom_paren), rule(atom_bracket), rule(atom_brace)) -DEF_RULE(atom_paren, c(atom_paren), and(3), tok(DEL_PAREN_OPEN), opt_rule(atom_2b), tok(DEL_PAREN_CLOSE)) -DEF_RULE_NC(atom_2b, or(2), rule(yield_expr), rule(testlist_comp)) -DEF_RULE(atom_bracket, c(atom_bracket), and(3), tok(DEL_BRACKET_OPEN), opt_rule(testlist_comp), tok(DEL_BRACKET_CLOSE)) -DEF_RULE(atom_brace, c(atom_brace), and(3), tok(DEL_BRACE_OPEN), opt_rule(dictorsetmaker), tok(DEL_BRACE_CLOSE)) +DEF_RULE_NC(atom, C_or(12), tok(NAME), tok(INTEGER), tok(FLOAT_OR_IMAG), tok(STRING), tok(BYTES), tok(ELLIPSIS), tok(KW_NONE), tok(KW_TRUE), tok(KW_FALSE), rule(atom_paren), rule(atom_bracket), rule(atom_brace)) +DEF_RULE(atom_paren, c(atom_paren), C_and(3), tok(DEL_PAREN_OPEN), opt_rule(atom_2b), tok(DEL_PAREN_CLOSE)) +DEF_RULE_NC(atom_2b, C_or(2), rule(yield_expr), rule(testlist_comp)) +DEF_RULE(atom_bracket, c(atom_bracket), C_and(3), tok(DEL_BRACKET_OPEN), opt_rule(testlist_comp), tok(DEL_BRACKET_CLOSE)) +DEF_RULE(atom_brace, c(atom_brace), C_and(3), tok(DEL_BRACE_OPEN), opt_rule(dictorsetmaker), tok(DEL_BRACE_CLOSE)) DEF_RULE_NC(testlist_comp, and_ident(2), rule(testlist_comp_2), opt_rule(testlist_comp_3)) -DEF_RULE_NC(testlist_comp_2, or(2), rule(star_expr), rule(namedexpr_test)) -DEF_RULE_NC(testlist_comp_3, or(2), rule(comp_for), rule(testlist_comp_3b)) +DEF_RULE_NC(testlist_comp_2, C_or(2), rule(star_expr), rule(namedexpr_test)) +DEF_RULE_NC(testlist_comp_3, C_or(2), rule(comp_for), rule(testlist_comp_3b)) DEF_RULE_NC(testlist_comp_3b, and_ident(2), tok(DEL_COMMA), opt_rule(testlist_comp_3c)) DEF_RULE_NC(testlist_comp_3c, list_with_end, rule(testlist_comp_2), tok(DEL_COMMA)) -DEF_RULE_NC(trailer, or(3), rule(trailer_paren), rule(trailer_bracket), rule(trailer_period)) -DEF_RULE(trailer_paren, c(trailer_paren), and(3), tok(DEL_PAREN_OPEN), opt_rule(arglist), tok(DEL_PAREN_CLOSE)) -DEF_RULE(trailer_bracket, c(trailer_bracket), and(3), tok(DEL_BRACKET_OPEN), rule(subscriptlist), tok(DEL_BRACKET_CLOSE)) -DEF_RULE(trailer_period, c(trailer_period), and(2), tok(DEL_PERIOD), tok(NAME)) +DEF_RULE_NC(trailer, C_or(3), rule(trailer_paren), rule(trailer_bracket), rule(trailer_period)) +DEF_RULE(trailer_paren, c(trailer_paren), C_and(3), tok(DEL_PAREN_OPEN), opt_rule(arglist), tok(DEL_PAREN_CLOSE)) +DEF_RULE(trailer_bracket, c(trailer_bracket), C_and(3), tok(DEL_BRACKET_OPEN), rule(subscriptlist), tok(DEL_BRACKET_CLOSE)) +DEF_RULE(trailer_period, c(trailer_period), C_and(2), tok(DEL_PERIOD), tok(NAME)) // subscriptlist: subscript (',' subscript)* [','] // subscript: test | [test] ':' [test] [sliceop] @@ -301,13 +301,13 @@ DEF_RULE(trailer_period, c(trailer_period), and(2), tok(DEL_PERIOD), tok(NAME)) #if MICROPY_PY_BUILTINS_SLICE DEF_RULE(subscriptlist, c(generic_tuple), list_with_end, rule(subscript), tok(DEL_COMMA)) -DEF_RULE_NC(subscript, or(2), rule(subscript_3), rule(subscript_2)) +DEF_RULE_NC(subscript, C_or(2), rule(subscript_3), rule(subscript_2)) DEF_RULE(subscript_2, c(subscript), and_ident(2), rule(test), opt_rule(subscript_3)) -DEF_RULE(subscript_3, c(subscript), and(2), tok(DEL_COLON), opt_rule(subscript_3b)) -DEF_RULE_NC(subscript_3b, or(2), rule(subscript_3c), rule(subscript_3d)) -DEF_RULE_NC(subscript_3c, and(2), tok(DEL_COLON), opt_rule(test)) +DEF_RULE(subscript_3, c(subscript), C_and(2), tok(DEL_COLON), opt_rule(subscript_3b)) +DEF_RULE_NC(subscript_3b, C_or(2), rule(subscript_3c), rule(subscript_3d)) +DEF_RULE_NC(subscript_3c, C_and(2), tok(DEL_COLON), opt_rule(test)) DEF_RULE_NC(subscript_3d, and_ident(2), rule(test), opt_rule(sliceop)) -DEF_RULE_NC(sliceop, and(2), tok(DEL_COLON), opt_rule(test)) +DEF_RULE_NC(sliceop, C_and(2), tok(DEL_COLON), opt_rule(test)) #else DEF_RULE(subscriptlist, c(generic_tuple), list_with_end, rule(test), tok(DEL_COMMA)) #endif @@ -317,17 +317,17 @@ DEF_RULE(subscriptlist, c(generic_tuple), list_with_end, rule(test), tok(DEL_COM // dictorsetmaker: (test ':' test (comp_for | (',' test ':' test)* [','])) | (test (comp_for | (',' test)* [','])) DEF_RULE_NC(exprlist, list_with_end, rule(exprlist_2), tok(DEL_COMMA)) -DEF_RULE_NC(exprlist_2, or(2), rule(star_expr), rule(expr)) +DEF_RULE_NC(exprlist_2, C_or(2), rule(star_expr), rule(expr)) DEF_RULE(testlist, c(generic_tuple), list_with_end, rule(test), tok(DEL_COMMA)) // TODO dictorsetmaker lets through more than is allowed DEF_RULE_NC(dictorsetmaker, and_ident(2), rule(dictorsetmaker_item), opt_rule(dictorsetmaker_tail)) #if MICROPY_PY_BUILTINS_SET DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and_ident(2), rule(test), opt_rule(generic_colon_test)) #else -DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), and(3), rule(test), tok(DEL_COLON), rule(test)) +DEF_RULE(dictorsetmaker_item, c(dictorsetmaker_item), C_and(3), rule(test), tok(DEL_COLON), rule(test)) #endif -DEF_RULE_NC(dictorsetmaker_tail, or(2), rule(comp_for), rule(dictorsetmaker_list)) -DEF_RULE_NC(dictorsetmaker_list, and(2), tok(DEL_COMMA), opt_rule(dictorsetmaker_list2)) +DEF_RULE_NC(dictorsetmaker_tail, C_or(2), rule(comp_for), rule(dictorsetmaker_list)) +DEF_RULE_NC(dictorsetmaker_list, C_and(2), tok(DEL_COMMA), opt_rule(dictorsetmaker_list2)) DEF_RULE_NC(dictorsetmaker_list2, list_with_end, rule(dictorsetmaker_item), tok(DEL_COMMA)) // classdef: 'class' NAME ['(' [arglist] ')'] ':' suite @@ -339,9 +339,9 @@ DEF_RULE_NC(classdef_2, and_ident(3), tok(DEL_PAREN_OPEN), opt_rule(arglist), to // TODO arglist lets through more than is allowed, compiler needs to do further verification DEF_RULE_NC(arglist, list_with_end, rule(arglist_2), tok(DEL_COMMA)) -DEF_RULE_NC(arglist_2, or(3), rule(arglist_star), rule(arglist_dbl_star), rule(argument)) -DEF_RULE_NC(arglist_star, and(2), tok(OP_STAR), rule(test)) -DEF_RULE_NC(arglist_dbl_star, and(2), tok(OP_DBL_STAR), rule(test)) +DEF_RULE_NC(arglist_2, C_or(3), rule(arglist_star), rule(arglist_dbl_star), rule(argument)) +DEF_RULE_NC(arglist_star, C_and(2), tok(OP_STAR), rule(test)) +DEF_RULE_NC(arglist_dbl_star, C_and(2), tok(OP_DBL_STAR), rule(test)) // # The reason that keywords are test nodes instead of NAME is that using NAME // # results in an ambiguity. ast.c makes sure it's a NAME. @@ -352,14 +352,14 @@ DEF_RULE_NC(arglist_dbl_star, and(2), tok(OP_DBL_STAR), rule(test)) DEF_RULE_NC(argument, and_ident(2), rule(test), opt_rule(argument_2)) #if MICROPY_PY_ASSIGN_EXPR -DEF_RULE_NC(argument_2, or(3), rule(comp_for), rule(generic_equal_test), rule(argument_3)) -DEF_RULE_NC(argument_3, and(2), tok(OP_ASSIGN), rule(test)) +DEF_RULE_NC(argument_2, C_or(3), rule(comp_for), rule(generic_equal_test), rule(argument_3)) +DEF_RULE_NC(argument_3, C_and(2), tok(OP_ASSIGN), rule(test)) #else -DEF_RULE_NC(argument_2, or(2), rule(comp_for), rule(generic_equal_test)) +DEF_RULE_NC(argument_2, C_or(2), rule(comp_for), rule(generic_equal_test)) #endif -DEF_RULE_NC(comp_iter, or(2), rule(comp_for), rule(comp_if)) +DEF_RULE_NC(comp_iter, C_or(2), rule(comp_for), rule(comp_if)) DEF_RULE_NC(comp_for, and_blank(5), tok(KW_FOR), rule(exprlist), tok(KW_IN), rule(or_test), opt_rule(comp_iter)) -DEF_RULE_NC(comp_if, and(3), tok(KW_IF), rule(test_nocond), opt_rule(comp_iter)) +DEF_RULE_NC(comp_if, C_and(3), tok(KW_IF), rule(test_nocond), opt_rule(comp_iter)) // # not used in grammar, but may appear in "node" passed from Parser to Compiler // encoding_decl: NAME @@ -367,6 +367,6 @@ DEF_RULE_NC(comp_if, and(3), tok(KW_IF), rule(test_nocond), opt_rule(comp_iter)) // yield_expr: 'yield' [yield_arg] // yield_arg: 'from' test | testlist -DEF_RULE(yield_expr, c(yield_expr), and(2), tok(KW_YIELD), opt_rule(yield_arg)) -DEF_RULE_NC(yield_arg, or(2), rule(yield_arg_from), rule(testlist)) -DEF_RULE_NC(yield_arg_from, and(2), tok(KW_FROM), rule(test)) +DEF_RULE(yield_expr, c(yield_expr), C_and(2), tok(KW_YIELD), opt_rule(yield_arg)) +DEF_RULE_NC(yield_arg, C_or(2), rule(yield_arg_from), rule(testlist)) +DEF_RULE_NC(yield_arg_from, C_and(2), tok(KW_FROM), rule(test)) diff --git a/py/lexer.c b/py/lexer.c index 7d2a251d4..98f36cced 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)) { @@ -362,7 +363,12 @@ STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw) { // 3MB of text; even gzip-compressed and with minimal structure, it'll take // roughly half a meg of storage. This form of Unicode escape may be added // later on, but it's definitely not a priority right now. -- CJA 20140607 +#if NO_NLR + mp_raise_NotImplementedError_o("unicode name escapes"); +#pragma message "TODO: can we just safely break and expect caller to handle exception?" +#else mp_raise_NotImplementedError(MP_ERROR_TEXT("unicode name escapes")); +#endif break; default: if (c >= '0' && c <= '7') { @@ -570,7 +576,7 @@ void mp_lexer_to_next(mp_lexer_t *lex) { for (size_t i = 0; i < MP_ARRAY_SIZE(tok_kw); i++) { int cmp = strcmp(s, tok_kw[i]); if (cmp == 0) { - lex->tok_kind = MP_TOKEN_KW_FALSE + i; + lex->tok_kind = (mp_token_kind_t)(MP_TOKEN_KW_FALSE + i); if (lex->tok_kind == MP_TOKEN_KW___DEBUG__) { lex->tok_kind = (MP_STATE_VM(mp_optimise_value) == 0 ? MP_TOKEN_KW_TRUE : MP_TOKEN_KW_FALSE); } @@ -677,7 +683,7 @@ void mp_lexer_to_next(mp_lexer_t *lex) { } // set token kind - lex->tok_kind = tok_enc_kind[tok_enc_index]; + lex->tok_kind = (mp_token_kind_t)tok_enc_kind[tok_enc_index]; // compute bracket level for implicit line joining if (lex->tok_kind == MP_TOKEN_DEL_PAREN_OPEN || lex->tok_kind == MP_TOKEN_DEL_BRACKET_OPEN || lex->tok_kind == MP_TOKEN_DEL_BRACE_OPEN) { @@ -692,6 +698,11 @@ void mp_lexer_to_next(mp_lexer_t *lex) { mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { mp_lexer_t *lex = m_new_obj(mp_lexer_t); +#if NO_NLR + if (lex == NULL) { + return NULL; + } +#endif lex->source_name = src_name; lex->reader = reader; lex->line = 1; @@ -701,6 +712,11 @@ mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { lex->alloc_indent_level = MICROPY_ALLOC_LEXER_INDENT_INIT; lex->num_indent_level = 1; lex->indent_level = m_new(uint16_t, lex->alloc_indent_level); +#if NO_NLR + if (lex->indent_level == NULL) { + return NULL; + } +#endif vstr_init(&lex->vstr, 32); // store sentinel for first indentation level @@ -722,6 +738,11 @@ mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { lex->tok_kind = MP_TOKEN_INDENT; } +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif return lex; } @@ -736,6 +757,11 @@ mp_lexer_t *mp_lexer_new_from_str_len(qstr src_name, const char *str, size_t len mp_lexer_t *mp_lexer_new_from_file(const char *filename) { mp_reader_t reader; mp_reader_new_file(&reader, filename); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return NULL; + } +#endif return mp_lexer_new(qstr_from_str(filename), reader); } diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index 9449a46ee..ad4f22d5e 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -7,10 +7,12 @@ from __future__ import print_function -import re -import sys import io import os +import re +import subprocess +import sys +import multiprocessing, multiprocessing.dummy # Extract MP_QSTR_FOO macros. @@ -20,6 +22,47 @@ _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 + + 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): if output: for m, r in [("/", "__"), ("\\", "__"), (":", "@"), ("..", "@@")]: @@ -44,7 +87,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) @@ -58,7 +101,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 "" @@ -104,7 +148,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) @@ -113,6 +157,37 @@ class Args: args = Args() args.command = sys.argv[1] + + if args.command == "pp": + named_args = { + s: [] + for s in [ + "pp", + "output", + "cflags", + "cxxflags", + "sources", + "changed_sources", + "dependencies", + ] + } + + for arg in sys.argv[1:]: + if arg in named_args: + current_tok = arg + else: + named_args[current_tok].append(arg) + + if not named_args["pp"] or len(named_args["output"]) != 1: + print("usage: %s %s ..." % (sys.argv[0], " ... ".join(named_args))) + sys.exit(2) + + for k, v in named_args.items(): + setattr(args, k, v) + + preprocess() + sys.exit(0) + args.mode = sys.argv[2] args.input_filename = sys.argv[3] # Unused for command=cat args.output_dir = sys.argv[4] diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 567b3b832..2f4bc9182 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() @@ -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 diff --git a/py/malloc.c b/py/malloc.c index c775d5b15..e760610d1 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -85,6 +85,9 @@ STATIC void *realloc_ext(void *ptr, size_t n_bytes, bool allow_move) { void *m_malloc(size_t num_bytes) { void *ptr = malloc(num_bytes); if (ptr == NULL && num_bytes != 0) { +#if NO_NLR + return +#endif m_malloc_fail(num_bytes); } #if MICROPY_MEM_STATS @@ -111,6 +114,9 @@ void *m_malloc_maybe(size_t num_bytes) { void *m_malloc_with_finaliser(size_t num_bytes) { void *ptr = malloc_with_finaliser(num_bytes); if (ptr == NULL && num_bytes != 0) { +#if NO_NLR + return +#endif m_malloc_fail(num_bytes); } #if MICROPY_MEM_STATS @@ -140,6 +146,9 @@ void *m_realloc(void *ptr, size_t new_num_bytes) { void *new_ptr = realloc(ptr, new_num_bytes); if (new_ptr == NULL && new_num_bytes != 0) { +#if NO_NLR + return +#endif m_malloc_fail(new_num_bytes); } #if MICROPY_MEM_STATS diff --git a/py/map.c b/py/map.c index 676c364da..e3acbbc3d 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 @@ -118,12 +107,16 @@ void mp_map_clear(mp_map_t *map) { map->table = NULL; } -STATIC void mp_map_rehash(mp_map_t *map) { +#if NO_NLR +STATIC int mp_map_rehash(mp_map_t *map) { size_t old_alloc = map->alloc; size_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1); DEBUG_printf("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n", map, old_alloc, new_alloc); mp_map_elem_t *old_table = map->table; mp_map_elem_t *new_table = m_new0(mp_map_elem_t, new_alloc); + if (new_table == NULL) { + return -1; + } // If we reach this point, table resizing succeeded, now we can edit the old map. map->alloc = new_alloc; map->used = 0; @@ -135,14 +128,44 @@ STATIC void mp_map_rehash(mp_map_t *map) { } } m_del(mp_map_elem_t, old_table, old_alloc); + return 0; // success } +#else +#error "please use no_nlr" +STATIC void mp_map_rehash(mp_map_t *map) { + size_t old_alloc = map->alloc; + size_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1); + DEBUG_printf("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n", map, old_alloc, new_alloc); + mp_map_elem_t *old_table = map->table; + mp_map_elem_t *new_table = m_new0(mp_map_elem_t, new_alloc); + // If we reach this point, table resizing succeeded, now we can edit the old map. + map->alloc = new_alloc; + map->used = 0; + map->all_keys_are_qstrs = 1; + map->table = new_table; + for (size_t i = 0; i < old_alloc; i++) { + if (old_table[i].key != MP_OBJ_NULL && old_table[i].key != MP_OBJ_SENTINEL) { + mp_map_elem_t lookup = mp_map_lookup(map, old_table[i].key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + if (lookup==NULL) { + //FIXME: maybe exception + return -1; + } + lookup->value = old_table[i].value; + } + } + m_del(mp_map_elem_t, old_table, old_alloc); +} +#endif // MP_MAP_LOOKUP behaviour: // - returns NULL if not found, else the slot it was found in with key,value non-null // MP_MAP_LOOKUP_ADD_IF_NOT_FOUND behaviour: // - returns slot, with key non-null and value=MP_OBJ_NULL if it was added // MP_MAP_LOOKUP_REMOVE_IF_FOUND behaviour: // - returns NULL if not found, else the slot if was found in with key null and value non-null +#if NO_NLR +#pragma message "TODO: NULL return can mean 1) not found; 2) exception" +#endif mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { // If the map is a fixed array then we must only be called for a lookup assert(!map->is_fixed || lookup_kind == MP_MAP_LOOKUP); @@ -209,9 +232,17 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t // map is a hash table (not an ordered array), so do a hash lookup + if (map->alloc == 0) { if (lookup_kind == MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { - mp_map_rehash(map); +#if NO_NLR + if (mp_map_rehash(map)) { + // exception + return NULL; + } +#else + mp_map_rehash(map); +#endif } else { return NULL; } @@ -222,7 +253,15 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t if (mp_obj_is_qstr(index)) { hash = qstr_hash(MP_OBJ_QSTR_VALUE(index)); } else { +#if NO_NLR + mp_obj_t hash_o = mp_unary_op(MP_UNARY_OP_HASH, index); + if (hash_o == MP_OBJ_NULL) { + return NULL; + } + hash = MP_OBJ_SMALL_INT_VALUE(hash_o); +#else hash = MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, index)); +#endif } size_t pos = hash % map->alloc; @@ -285,7 +324,14 @@ mp_map_elem_t *mp_map_lookup(mp_map_t *map, mp_obj_t index, mp_map_lookup_kind_t return avail_slot; } else { // not enough room in table, rehash it +#if NO_NLR + if (mp_map_rehash(map)) { + // exception + return NULL; + } +#else mp_map_rehash(map); +#endif // restart the search for the new element start_pos = pos = hash % map->alloc; } @@ -321,6 +367,9 @@ STATIC void mp_set_rehash(mp_set_t *set) { m_del(mp_obj_t, old_table, old_alloc); } +#if NO_NLR +#pragma message "TODO: MP_OBJ_NULL return can mean 1) not found; 2) exception" +#endif mp_obj_t mp_set_lookup(mp_set_t *set, mp_obj_t index, mp_map_lookup_kind_t lookup_kind) { // Note: lookup_kind can be MP_MAP_LOOKUP_ADD_IF_NOT_FOUND_OR_REMOVE_IF_FOUND which // is handled by using bitwise operations. @@ -332,7 +381,15 @@ mp_obj_t mp_set_lookup(mp_set_t *set, mp_obj_t index, mp_map_lookup_kind_t looku return MP_OBJ_NULL; } } +#if NO_NLR + mp_obj_t hash_o = mp_unary_op(MP_UNARY_OP_HASH, index); + if (hash_o == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + mp_uint_t hash = MP_OBJ_SMALL_INT_VALUE(hash_o); +#else mp_uint_t hash = MP_OBJ_SMALL_INT_VALUE(mp_unary_op(MP_UNARY_OP_HASH, index)); +#endif size_t pos = hash % set->alloc; size_t start_pos = pos; mp_obj_t *avail_slot = NULL; diff --git a/py/misc.h b/py/misc.h index 2dd20365c..a1d7b3ab8 100644 --- a/py/misc.h +++ b/py/misc.h @@ -37,6 +37,8 @@ typedef unsigned char byte; typedef unsigned int uint; +//#include // for mpuint_t + /** generic ops *************************************************/ #ifndef MIN @@ -53,6 +55,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) @@ -97,7 +102,11 @@ void *m_realloc(void *ptr, size_t new_num_bytes); void *m_realloc_maybe(void *ptr, size_t new_num_bytes, bool allow_move); void m_free(void *ptr); #endif +#if NO_NLR +void *m_malloc_fail(size_t num_bytes); +#else NORETURN void m_malloc_fail(size_t num_bytes); +#endif #if MICROPY_MEM_STATS size_t m_get_total_bytes_allocated(void); @@ -191,8 +200,14 @@ char *vstr_add_len(vstr_t *vstr, size_t len); char *vstr_null_terminated_str(vstr_t *vstr); void vstr_add_byte(vstr_t *vstr, byte v); void vstr_add_char(vstr_t *vstr, unichar chr); +#if NO_NLR +//#pragma message "BEWARE: non void return is problematic with function pointers" +int vstr_add_str(vstr_t *vstr, const char *str); +int vstr_add_strn(vstr_t *vstr, const char *str, size_t len); +#else void vstr_add_str(vstr_t *vstr, const char *str); void vstr_add_strn(vstr_t *vstr, const char *str, size_t len); +#endif void vstr_ins_byte(vstr_t *vstr, size_t byte_pos, byte b); void vstr_ins_char(vstr_t *vstr, size_t char_pos, unichar chr); void vstr_cut_head_bytes(vstr_t *vstr, size_t bytes_to_cut); @@ -272,7 +287,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 diff --git a/py/mkrules.cmake b/py/mkrules.cmake new file mode 100644 index 000000000..9c4c4afab --- /dev/null +++ b/py/mkrules.cmake @@ -0,0 +1,135 @@ +# CMake fragment for MicroPython rules + +set(MICROPY_GENHDR_DIR "${CMAKE_BINARY_DIR}/genhdr") +set(MICROPY_MPVERSION "${MICROPY_GENHDR_DIR}/mpversion.h") +set(MICROPY_MODULEDEFS "${MICROPY_GENHDR_DIR}/moduledefs.h") +set(MICROPY_QSTRDEFS_PY "${MICROPY_PY_DIR}/qstrdefs.h") +set(MICROPY_QSTRDEFS_LAST "${MICROPY_GENHDR_DIR}/qstr.i.last") +set(MICROPY_QSTRDEFS_SPLIT "${MICROPY_GENHDR_DIR}/qstr.split") +set(MICROPY_QSTRDEFS_COLLECTED "${MICROPY_GENHDR_DIR}/qstrdefs.collected.h") +set(MICROPY_QSTRDEFS_PREPROCESSED "${MICROPY_GENHDR_DIR}/qstrdefs.preprocessed.h") +set(MICROPY_QSTRDEFS_GENERATED "${MICROPY_GENHDR_DIR}/qstrdefs.generated.h") + +# Provide defaults for preprocessor flags if not already defined +if(NOT MICROPY_CPP_FLAGS) + get_target_property(MICROPY_CPP_INC ${MICROPY_TARGET} INCLUDE_DIRECTORIES) + get_target_property(MICROPY_CPP_DEF ${MICROPY_TARGET} COMPILE_DEFINITIONS) +endif() + +# Compute MICROPY_CPP_FLAGS for preprocessor +list(APPEND MICROPY_CPP_INC ${MICROPY_CPP_INC_EXTRA}) +list(APPEND MICROPY_CPP_DEF ${MICROPY_CPP_DEF_EXTRA}) +set(_prefix "-I") +foreach(_arg ${MICROPY_CPP_INC}) + list(APPEND MICROPY_CPP_FLAGS ${_prefix}${_arg}) +endforeach() +set(_prefix "-D") +foreach(_arg ${MICROPY_CPP_DEF}) + list(APPEND MICROPY_CPP_FLAGS ${_prefix}${_arg}) +endforeach() +list(APPEND MICROPY_CPP_FLAGS ${MICROPY_CPP_FLAGS_EXTRA}) + +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +target_sources(${MICROPY_TARGET} PRIVATE + ${MICROPY_MPVERSION} + ${MICROPY_QSTRDEFS_GENERATED} +) + +# Command to force the build of another command + +add_custom_command( + OUTPUT MICROPY_FORCE_BUILD + COMMENT "" + COMMAND echo -n +) + +# Generate mpversion.h + +add_custom_command( + OUTPUT ${MICROPY_MPVERSION} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MICROPY_GENHDR_DIR} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_DIR}/py/makeversionhdr.py ${MICROPY_MPVERSION} + DEPENDS MICROPY_FORCE_BUILD +) + +# Generate moduledefs.h + +add_custom_command( + OUTPUT ${MICROPY_MODULEDEFS} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makemoduledefs.py --vpath="/" ${MICROPY_SOURCE_QSTR} > ${MICROPY_MODULEDEFS} + DEPENDS ${MICROPY_MPVERSION} + ${MICROPY_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 MICROPY_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 ${MICROPY_QSTRDEFS_LAST} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py pp ${CMAKE_C_COMPILER} -E output ${MICROPY_GENHDR_DIR}/qstr.i.last cflags ${MICROPY_CPP_FLAGS} -DNO_QSTR sources ${MICROPY_SOURCE_QSTR} + DEPENDS ${MICROPY_MODULEDEFS} + ${MICROPY_SOURCE_QSTR} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_QSTRDEFS_SPLIT} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py split qstr ${MICROPY_GENHDR_DIR}/qstr.i.last ${MICROPY_GENHDR_DIR}/qstr _ + COMMAND touch ${MICROPY_QSTRDEFS_SPLIT} + DEPENDS ${MICROPY_QSTRDEFS_LAST} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_QSTRDEFS_COLLECTED} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py cat qstr _ ${MICROPY_GENHDR_DIR}/qstr ${MICROPY_QSTRDEFS_COLLECTED} + DEPENDS ${MICROPY_QSTRDEFS_SPLIT} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_QSTRDEFS_PREPROCESSED} + COMMAND cat ${MICROPY_QSTRDEFS_PY} ${MICROPY_QSTRDEFS_PORT} ${MICROPY_QSTRDEFS_COLLECTED} | sed "s/^Q(.*)/\"&\"/" | ${CMAKE_C_COMPILER} -E ${MICROPY_CPP_FLAGS} - | sed "s/^\\\"\\(Q(.*)\\)\\\"/\\1/" > ${MICROPY_QSTRDEFS_PREPROCESSED} + DEPENDS ${MICROPY_QSTRDEFS_PY} + ${MICROPY_QSTRDEFS_PORT} + ${MICROPY_QSTRDEFS_COLLECTED} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_QSTRDEFS_GENERATED} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdata.py ${MICROPY_QSTRDEFS_PREPROCESSED} > ${MICROPY_QSTRDEFS_GENERATED} + DEPENDS ${MICROPY_QSTRDEFS_PREPROCESSED} + VERBATIM + COMMAND_EXPAND_LISTS +) + +# Build frozen code if enabled + +if(MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_CONTENT "${CMAKE_BINARY_DIR}/frozen_content.c") + + target_sources(${MICROPY_TARGET} PRIVATE + ${MICROPY_FROZEN_CONTENT} + ) + + target_compile_definitions(${MICROPY_TARGET} PUBLIC + MICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool + MICROPY_MODULE_FROZEN_MPY=\(1\) + ) + + add_custom_command( + OUTPUT ${MICROPY_FROZEN_CONTENT} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_DIR}/tools/makemanifest.py -o ${MICROPY_FROZEN_CONTENT} -v "MPY_DIR=${MICROPY_DIR}" -v "PORT_DIR=${MICROPY_PORT_DIR}" -b "${CMAKE_BINARY_DIR}" -f${MICROPY_CROSS_FLAGS} ${MICROPY_FROZEN_MANIFEST} + DEPENDS MICROPY_FORCE_BUILD + ${MICROPY_QSTRDEFS_GENERATED} + VERBATIM + ) +endif() diff --git a/py/mkrules.mk b/py/mkrules.mk index c37c25db4..eb8477cbb 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 # 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 @@ -58,11 +60,25 @@ $(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 $<" @@ -79,14 +95,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 $@" @@ -197,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: diff --git a/py/modbuiltins.c b/py/modbuiltins.c index cfbfc5a25..53ef356e9 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -23,6 +23,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include @@ -624,7 +627,8 @@ STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { // built-in core functions { MP_ROM_QSTR(MP_QSTR___build_class__), MP_ROM_PTR(&mp_builtin___build_class___obj) }, { MP_ROM_QSTR(MP_QSTR___import__), MP_ROM_PTR(&mp_builtin___import___obj) }, - { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, +// { MP_ROM_QSTR(MP_QSTR___repl_print__), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, + { MP_ROM_QSTR(MP_QSTR_displayhook), MP_ROM_PTR(&mp_builtin___repl_print___obj) }, // built-in types { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_PTR(&mp_type_bool) }, diff --git a/py/modgc.c b/py/modgc.c index 534a711c1..a0d4ec545 100644 --- a/py/modgc.c +++ b/py/modgc.c @@ -64,7 +64,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(gc_isenabled_obj, gc_isenabled); STATIC mp_obj_t gc_mem_free(void) { gc_info_t info; gc_info(&info); - return MP_OBJ_NEW_SMALL_INT(info.free); + return MP_OBJ_NEW_SMALL_INT(info.gc_free); } MP_DEFINE_CONST_FUN_OBJ_0(gc_mem_free_obj, gc_mem_free); diff --git a/py/modio.c b/py/modio.c index 8a18357e2..07c5a5b33 100644 --- a/py/modio.c +++ b/py/modio.c @@ -54,6 +54,21 @@ STATIC mp_obj_t iobase_make_new(const mp_obj_type_t *type, size_t n_args, size_t return MP_OBJ_FROM_PTR(&iobase_singleton); } +#if NO_NLR +STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode, qstr qst) { + mp_obj_t dest[3]; + mp_load_method(obj, qst, dest); + mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, size, buf}; + dest[2] = MP_OBJ_FROM_PTR(&ar); + mp_obj_t ret = mp_call_method_n_kw(1, 0, dest); + if (ret == mp_const_none) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } else { + return mp_obj_get_int(ret); + } +} +#else STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode, qstr qst) { mp_obj_t dest[3]; mp_load_method(obj, qst, dest); @@ -72,6 +87,7 @@ STATIC mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int return MP_STREAM_ERROR; } } +#endif // NO_NLR STATIC mp_uint_t iobase_read(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode) { return iobase_read_write(obj, buf, size, errcode, MP_QSTR_readinto); } @@ -231,7 +247,12 @@ STATIC mp_obj_t resource_stream(mp_obj_t package_in, mp_obj_t path_in) { vstr_add_strn(&path_buf, path, len); len = path_buf.len; +#if __ARDUINO__ + #pragma message "cannot link mp_find_frozen_str" + const char *data = NULL; +#else const char *data = mp_find_frozen_str(path_buf.buf, &len); +#endif if (data != NULL) { mp_obj_stringio_t *o = m_new_obj(mp_obj_stringio_t); o->base.type = &mp_type_bytesio; diff --git a/py/modmath.c b/py/modmath.c index b7948f39e..7e3ace5bc 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -37,7 +37,11 @@ #define MP_PI_4 MICROPY_FLOAT_CONST(0.78539816339744830962) #define MP_3_PI_4 MICROPY_FLOAT_CONST(2.35619449019234492885) +#if NO_NLR +STATIC mp_obj_t math_error(void) { +#else STATIC NORETURN void math_error(void) { +#endif mp_raise_ValueError(MP_ERROR_TEXT("math domain error")); } @@ -45,6 +49,9 @@ STATIC mp_obj_t math_generic_1(mp_obj_t x_obj, mp_float_t (*f)(mp_float_t)) { mp_float_t x = mp_obj_get_float(x_obj); mp_float_t ans = f(x); if ((isnan(ans) && !isnan(x)) || (isinf(ans) && !isinf(x))) { +#if NO_NLR + return +#endif math_error(); } return mp_obj_new_float(ans); @@ -55,6 +62,9 @@ STATIC mp_obj_t math_generic_2(mp_obj_t x_obj, mp_obj_t y_obj, mp_float_t (*f)(m mp_float_t y = mp_obj_get_float(y_obj); mp_float_t ans = f(x, y); if ((isnan(ans) && !isnan(x) && !isnan(y)) || (isinf(ans) && !isinf(x))) { +#if NO_NLR + return +#endif math_error(); } return mp_obj_new_float(ans); @@ -170,7 +180,7 @@ STATIC mp_float_t MICROPY_FLOAT_C_FUN(fabs_func)(mp_float_t x) { } MATH_FUN_1(fabs, fabs_func) // floor(x) -MATH_FUN_1_TO_INT(floor, floor) // TODO: delegate to x.__floor__() if x is not a float +MATH_FUN_1_TO_INT(floor, floor) //TODO: delegate to x.__floor__() if x is not a float // fmod(x, y) #if MICROPY_PY_MATH_FMOD_FIX_INFNAN mp_float_t fmod_func(mp_float_t x, mp_float_t y) { @@ -204,21 +214,22 @@ 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}, - {MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ}, {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); if (rel_tol < (mp_float_t)0.0 || abs_tol < (mp_float_t)0.0) { +#if NO_NLR + return +#endif math_error(); } if (a == b) { @@ -244,6 +255,9 @@ MP_DEFINE_CONST_FUN_OBJ_KW(mp_math_isclose_obj, 2, mp_math_isclose); STATIC mp_obj_t mp_math_log(size_t n_args, const mp_obj_t *args) { mp_float_t x = mp_obj_get_float(args[0]); if (x <= (mp_float_t)0.0) { +#if NO_NLR + return +#endif math_error(); } mp_float_t l = MICROPY_FLOAT_C_FUN(log)(x); @@ -252,6 +266,9 @@ STATIC mp_obj_t mp_math_log(size_t n_args, const mp_obj_t *args) { } else { mp_float_t base = mp_obj_get_float(args[1]); if (base <= (mp_float_t)0.0) { +#if NO_NLR + return +#endif math_error(); } else if (base == (mp_float_t)1.0) { mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); diff --git a/py/modmicropython.c b/py/modmicropython.c index f7eadf79b..e62256d39 100644 --- a/py/modmicropython.c +++ b/py/modmicropython.c @@ -32,6 +32,9 @@ #include "py/gc.h" #include "py/mphal.h" +// #include "lib/utils/interrupt_char.h" // for mp_hal_set_interrupt_char +extern void mp_hal_set_interrupt_char(int c); + // Various builtins specific to MicroPython runtime, // living in micropython module diff --git a/py/modstruct.c b/py/modstruct.c index 4cbcad6d4..7bea6d2b4 100644 --- a/py/modstruct.c +++ b/py/modstruct.c @@ -99,6 +99,11 @@ STATIC size_t calc_size_items(const char *fmt, size_t *total_sz) { total_cnt += cnt; size_t align; size_t sz = mp_binary_get_size(fmt_type, *fmt, &align); +#if NO_NLR + if (sz == 0) { + return (size_t)-1; + } +#endif while (cnt--) { // Apply alignment size = (size + align - 1) & ~(align - 1); @@ -112,8 +117,19 @@ STATIC size_t calc_size_items(const char *fmt, size_t *total_sz) { STATIC mp_obj_t struct_calcsize(mp_obj_t fmt_in) { const char *fmt = mp_obj_str_get_str(fmt_in); +#if NO_NLR + if (fmt == NULL) { + return MP_OBJ_NULL; + } +#endif size_t size; +#if NO_NLR + if (calc_size_items(fmt, &size) == (size_t)-1) { + return MP_OBJ_NULL; + } +#else calc_size_items(fmt, &size); +#endif return MP_OBJ_NEW_SMALL_INT(size); } MP_DEFINE_CONST_FUN_OBJ_1(struct_calcsize_obj, struct_calcsize); @@ -126,6 +142,11 @@ STATIC mp_obj_t struct_unpack_from(size_t n_args, const mp_obj_t *args) { const char *fmt = mp_obj_str_get_str(args[0]); size_t total_sz; size_t num_items = calc_size_items(fmt, &total_sz); +#if NO_NLR + if (num_items == (size_t)-1) { + return MP_OBJ_NULL; + } +#endif char fmt_type = get_fmt_type(&fmt); mp_obj_tuple_t *res = MP_OBJ_TO_PTR(mp_obj_new_tuple(num_items, NULL)); mp_buffer_info_t bufinfo; @@ -214,6 +235,13 @@ STATIC void struct_pack_into_internal(mp_obj_t fmt_in, byte *p, size_t n_args, c STATIC mp_obj_t struct_pack(size_t n_args, const mp_obj_t *args) { // TODO: "The arguments must match the values required by the format exactly." +#pragma message "TODO: The arguments must match the values required by the format exactly." +#if NO_NLR + mp_obj_t size_obj = struct_calcsize(args[0]); + if (size_obj == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif mp_int_t size = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); vstr_t vstr; vstr_init_len(&vstr, size); @@ -240,6 +268,12 @@ STATIC mp_obj_t struct_pack_into(size_t n_args, const mp_obj_t *args) { p += offset; // Check that the output buffer is big enough to hold all the values +#if NO_NLR + mp_obj_t sz_obj = struct_calcsize(args[0]); + if (sz_obj == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif mp_int_t sz = MP_OBJ_SMALL_INT_VALUE(struct_calcsize(args[0])); if (p + sz > end_p) { mp_raise_ValueError(MP_ERROR_TEXT("buffer too small")); diff --git a/py/modsys.c b/py/modsys.c index 586b13e74..58f3b7c2d 100644 --- a/py/modsys.c +++ b/py/modsys.c @@ -116,7 +116,7 @@ STATIC mp_obj_t mp_sys_exit(size_t n_args, const mp_obj_t *args) { } else { exc = mp_obj_new_exception_arg1(&mp_type_SystemExit, args[0]); } - nlr_raise(exc); + mp_raise_or_return(exc); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_sys_exit_obj, 0, 1, mp_sys_exit); @@ -124,7 +124,13 @@ STATIC mp_obj_t mp_sys_print_exception(size_t n_args, const mp_obj_t *args) { #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES void *stream_obj = &mp_sys_stdout_obj; if (n_args > 1) { +#if NO_NLR + if (mp_get_stream_raise(args[1], MP_STREAM_OP_WRITE) == NULL) { + return MP_OBJ_NULL; + } +#else mp_get_stream_raise(args[1], MP_STREAM_OP_WRITE); +#endif stream_obj = MP_OBJ_TO_PTR(args[1]); } @@ -238,6 +244,10 @@ STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_getsizeof), MP_ROM_PTR(&mp_sys_getsizeof_obj) }, #endif + #if __ANDROID__ + { MP_ROM_QSTR(MP_QSTR_getandroidapilevel), MP_ROM_PTR(19) }, + #endif + /* * Extensions to CPython */ diff --git a/py/modthread.c b/py/modthread.c index 1306dc642..805c4cb7e 100644 --- a/py/modthread.c +++ b/py/modthread.c @@ -175,6 +175,9 @@ STATIC void *thread_entry(void *args_in) { mp_locals_set(args->dict_locals); mp_globals_set(args->dict_globals); +#if NO_NLR + MP_STATE_THREAD(active_exception) = NULL; +#endif MP_THREAD_GIL_ENTER(); // signal that we are set up and running @@ -186,14 +189,23 @@ STATIC void *thread_entry(void *args_in) { DEBUG_printf("[thread] start ts=%p args=%p stack=%p\n", &ts, &args, MP_STATE_THREAD(stack_top)); +#if NO_NLR + mp_call_function_n_kw(args->fun, args->n_args, args->n_kw, args->args); + if (MP_STATE_THREAD(active_exception) != NULL) { +#else nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_call_function_n_kw(args->fun, args->n_args, args->n_kw, args->args); nlr_pop(); } else { +#endif // uncaught exception // check for SystemExit +#if NO_NLR + mp_obj_base_t *exc = (mp_obj_base_t *)MP_STATE_THREAD(active_exception); +#else mp_obj_base_t *exc = (mp_obj_base_t *)nlr.ret_val; +#endif if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // swallow exception silently } else { diff --git a/py/mpconfig.h b/py/mpconfig.h index 12517316c..a9404d562 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 @@ -126,7 +126,7 @@ // Number of bytes in memory allocation/GC block. Any size allocated will be // rounded up to be multiples of this. #ifndef MICROPY_BYTES_PER_GC_BLOCK -#define MICROPY_BYTES_PER_GC_BLOCK (4 * BYTES_PER_WORD) +#define MICROPY_BYTES_PER_GC_BLOCK (4 * MP_BYTES_PER_OBJ_WORD) #endif // Number of words allocated (in BSS) to the GC stack (minimum is 1) @@ -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 @@ -304,6 +309,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) @@ -339,9 +349,11 @@ #define MICROPY_EMIT_XTENSAWIN (0) #endif +#if NO_NLR +#else // Convenience definition for whether any native emitter is enabled #define MICROPY_EMIT_NATIVE (MICROPY_EMIT_X64 || MICROPY_EMIT_X86 || MICROPY_EMIT_THUMB || MICROPY_EMIT_ARM || MICROPY_EMIT_XTENSA || MICROPY_EMIT_XTENSAWIN) - +#endif // Select prelude-as-bytes-object for certain emitters #define MICROPY_EMIT_NATIVE_PRELUDE_AS_BYTES_OBJ (MICROPY_EMIT_XTENSAWIN) @@ -451,8 +463,18 @@ // Whether to enable debugging versions of MP_OBJ_NULL/STOP_ITERATION/SENTINEL #ifndef MICROPY_DEBUG_MP_OBJ_SENTINELS +#if NO_NLR +// Note: this is currently required for no NLR, to distinguish MP_OBJ_NULL (exception) from MP_OBJ_STOP_ITERATION +#define MICROPY_DEBUG_MP_OBJ_SENTINELS (1) +#else #define MICROPY_DEBUG_MP_OBJ_SENTINELS (0) #endif +#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 @@ -1515,17 +1537,17 @@ typedef double mp_float_t; #define STATIC static #endif -// Number of bytes in a word -#ifndef BYTES_PER_WORD -#define BYTES_PER_WORD (sizeof(mp_uint_t)) +// Number of bytes in an object word: mp_obj_t, mp_uint_t, mp_uint_t +#ifndef MP_BYTES_PER_OBJ_WORD +#define MP_BYTES_PER_OBJ_WORD (sizeof(mp_uint_t)) #endif -#ifndef BITS_PER_BYTE -#define BITS_PER_BYTE (8) +// Number of bits in a byte +#ifndef MP_BITS_PER_BYTE +#define MP_BITS_PER_BYTE (8) #endif -#define BITS_PER_WORD (BITS_PER_BYTE * BYTES_PER_WORD) // mp_int_t value with most significant bit set -#define WORD_MSBIT_HIGH (((mp_uint_t)1) << (BYTES_PER_WORD * 8 - 1)) +#define MP_OBJ_WORD_MSBIT_HIGH (((mp_uint_t)1) << (MP_BYTES_PER_OBJ_WORD * MP_BITS_PER_BYTE - 1)) // Make sure both MP_ENDIANNESS_LITTLE and MP_ENDIANNESS_BIG are // defined and that they are the opposite of each other. @@ -1638,6 +1660,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/mpprint.c b/py/mpprint.c index c550c1d95..307367451 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -376,13 +376,6 @@ int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, c } #endif -int mp_printf(const mp_print_t *print, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int ret = mp_vprintf(print, fmt, ap); - va_end(ap); - return ret; -} int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { int chrs = 0; @@ -484,10 +477,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 +492,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': { @@ -557,11 +551,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: @@ -575,3 +567,12 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { } return chrs; } + +int mp_printf(const mp_print_t *print, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = mp_vprintf(print, fmt, ap); + va_end(ap); + return ret; +} + diff --git a/py/mpstate.c b/py/mpstate.c index 6ce64adfd..c18325e44 100644 --- a/py/mpstate.c +++ b/py/mpstate.c @@ -30,4 +30,4 @@ mp_dynamic_compiler_t mp_dynamic_compiler = {0}; #endif -mp_state_ctx_t mp_state_ctx; +extern mp_state_ctx_t mp_state_ctx; diff --git a/py/mpstate.h b/py/mpstate.h index 2519c77e2..ab08d1794 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -262,7 +262,11 @@ typedef struct _mp_state_thread_t { mp_obj_dict_t *dict_locals; mp_obj_dict_t *dict_globals; +#if NO_NLR + mp_obj_base_t *active_exception; +#else nlr_buf_t *nlr_top; +#endif #if MICROPY_PY_SYS_SETTRACE mp_obj_t prof_trace_callback; diff --git a/py/mpz.c b/py/mpz.c index b3d880679..e0d249c21 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -531,60 +531,37 @@ STATIC void mpn_div(mpz_dig_t *num_dig, size_t *num_len, const mpz_dig_t *den_di quo /= lead_den_digit; // Multiply quo by den and subtract from num to get remainder. - // We have different code here to handle different compile-time - // configurations of mpz: - // - // 1. DIG_SIZE is stricly less than half the number of bits - // available in mpz_dbl_dig_t. In this case we can use a - // slightly more optimal (in time and space) routine that - // uses the extra bits in mpz_dbl_dig_signed_t to store a - // sign bit. - // - // 2. DIG_SIZE is exactly half the number of bits available in - // mpz_dbl_dig_t. In this (common) case we need to be careful - // not to overflow the borrow variable. And the shifting of - // borrow needs some special logic (it's a shift right with - // round up). - // + // Must be careful with overflow of the borrow variable. Both + // borrow and low_digs are signed values and need signed right-shift, + // but x is unsigned and may take a full-range value. const mpz_dig_t *d = den_dig; mpz_dbl_dig_t d_norm = 0; - mpz_dbl_dig_t borrow = 0; + mpz_dbl_dig_signed_t borrow = 0; for (mpz_dig_t *n = num_dig - den_len; n < num_dig; ++n, ++d) { + // Get the next digit in (den). d_norm = ((mpz_dbl_dig_t)*d << norm_shift) | (d_norm >> DIG_SIZE); + // Multiply the next digit in (quo * den). mpz_dbl_dig_t x = (mpz_dbl_dig_t)quo * (d_norm & DIG_MASK); - #if DIG_SIZE < MPZ_DBL_DIG_SIZE / 2 - borrow += (mpz_dbl_dig_t)*n - x; // will overflow if DIG_SIZE >= MPZ_DBL_DIG_SIZE/2 - *n = borrow & DIG_MASK; - borrow = (mpz_dbl_dig_signed_t)borrow >> DIG_SIZE; - #else // DIG_SIZE == MPZ_DBL_DIG_SIZE / 2 - if (x >= *n || *n - x <= borrow) { - borrow += x - (mpz_dbl_dig_t)*n; - *n = (-borrow) & DIG_MASK; - borrow = (borrow >> DIG_SIZE) + ((borrow & DIG_MASK) == 0 ? 0 : 1); // shift-right with round-up - } else { - *n = ((mpz_dbl_dig_t)*n - x - borrow) & DIG_MASK; - borrow = 0; - } - #endif + // Compute the low DIG_MASK bits of the next digit in (num - quo * den) + mpz_dbl_dig_signed_t low_digs = (borrow & DIG_MASK) + *n - (x & DIG_MASK); + // Store the digit result for (num). + *n = low_digs & DIG_MASK; + // Compute the borrow, shifted right before summing to avoid overflow. + borrow = (borrow >> DIG_SIZE) - (x >> DIG_SIZE) + (low_digs >> DIG_SIZE); } - #if DIG_SIZE < MPZ_DBL_DIG_SIZE / 2 - // Borrow was negative in the above for-loop, make it positive for next if-block. - borrow = -borrow; - #endif - // At this point we have either: // // 1. quo was the correct value and the most-sig-digit of num is exactly - // cancelled by borrow (borrow == *num_dig). In this case there is + // cancelled by borrow (borrow + *num_dig == 0). In this case there is // nothing more to do. // // 2. quo was too large, we subtracted too many den from num, and the - // most-sig-digit of num is 1 less than borrow (borrow == *num_dig + 1). + // most-sig-digit of num is less than needed (borrow + *num_dig < 0). // In this case we must reduce quo and add back den to num until the // carry from this operation cancels out the borrow. // - borrow -= *num_dig; + borrow += *num_dig; for (; borrow != 0; --quo) { d = den_dig; d_norm = 0; @@ -595,7 +572,7 @@ STATIC void mpn_div(mpz_dig_t *num_dig, size_t *num_len, const mpz_dig_t *den_di *n = carry & DIG_MASK; carry >>= DIG_SIZE; } - borrow -= carry; + borrow += carry; } // store this digit of the quotient @@ -1573,7 +1550,7 @@ bool mpz_as_int_checked(const mpz_t *i, mp_int_t *value) { mpz_dig_t *d = i->dig + i->len; while (d-- > i->dig) { - if (val > (~(WORD_MSBIT_HIGH) >> DIG_SIZE)) { + if (val > (~(MP_OBJ_WORD_MSBIT_HIGH) >> DIG_SIZE)) { // will overflow return false; } @@ -1598,7 +1575,7 @@ bool mpz_as_uint_checked(const mpz_t *i, mp_uint_t *value) { mpz_dig_t *d = i->dig + i->len; while (d-- > i->dig) { - if (val > (~(WORD_MSBIT_HIGH) >> (DIG_SIZE - 1))) { + if (val > (~(MP_OBJ_WORD_MSBIT_HIGH) >> (DIG_SIZE - 1))) { // will overflow return false; } @@ -1609,7 +1586,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 +1617,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/nativeglue.c b/py/nativeglue.c index 30e5b4006..4ad162d6f 100644 --- a/py/nativeglue.c +++ b/py/nativeglue.c @@ -159,12 +159,55 @@ STATIC mp_obj_t mp_native_call_function_n_kw(mp_obj_t fun_in, size_t n_args_kw, // wrapper that makes raise obj and raises it // END_FINALLY opcode requires that we don't raise if o==None +#if NO_NLR +STATIC mp_obj_t mp_native_raise(mp_obj_t o) { + if (o != MP_OBJ_NULL && o != mp_const_none) { + return mp_raise_o(mp_make_raise_obj(o)); + } + return MP_OBJ_SENTINEL; +} + +STATIC mp_obj_t mp_native_is_exc(void) { + if (MP_STATE_THREAD(active_exception) == NULL) { + return MP_OBJ_SENTINEL; + } else { + return MP_OBJ_NULL; + } +} + +STATIC mp_obj_t mp_native_get_exc(void) { + return MP_STATE_THREAD(active_exception); +} + +STATIC void mp_native_clr_exc(void) { + MP_STATE_THREAD(active_exception) = NULL; +} + +// wrapper that handles iterator buffer +STATIC mp_obj_t mp_native_getiter(mp_obj_t obj, mp_obj_iter_buf_t *iter) { + if (iter == NULL) { + return mp_getiter(obj, NULL); + } else { + obj = mp_getiter(obj, iter); + if (obj == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } + if (obj != MP_OBJ_FROM_PTR(iter)) { + // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. + iter->base.type = MP_OBJ_NULL; + iter->buf[0] = obj; + } + return MP_OBJ_SENTINEL; + } +} +#else STATIC void mp_native_raise(mp_obj_t o) { if (o != MP_OBJ_NULL && o != mp_const_none) { nlr_raise(mp_make_raise_obj(o)); } } + // wrapper that handles iterator buffer STATIC mp_obj_t mp_native_getiter(mp_obj_t obj, mp_obj_iter_buf_t *iter) { if (iter == NULL) { @@ -179,6 +222,7 @@ STATIC mp_obj_t mp_native_getiter(mp_obj_t obj, mp_obj_iter_buf_t *iter) { return NULL; } } +#endif // wrapper that handles iterator buffer STATIC mp_obj_t mp_native_iternext(mp_obj_iter_buf_t *iter) { @@ -191,6 +235,38 @@ STATIC mp_obj_t mp_native_iternext(mp_obj_iter_buf_t *iter) { return mp_iternext(obj); } +#if NO_NLR +STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value) { + mp_obj_t throw_value = *ret_value; + if (throw_value != MP_OBJ_NULL) { + send_value = MP_OBJ_NULL; + } + mp_vm_return_kind_t ret_kind = mp_resume(gen, send_value, throw_value, ret_value); + + if (ret_kind == MP_VM_RETURN_YIELD) { + return true; + } else if (ret_kind == MP_VM_RETURN_NORMAL) { + if (*ret_value == MP_OBJ_STOP_ITERATION) { + *ret_value = mp_const_none; + } + } else { + assert(ret_kind == MP_VM_RETURN_EXCEPTION); + if (!mp_obj_exception_match(*ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { +#pragma message "caller must also check active_exception" + mp_raise_o(*ret_value); + return false; + } + *ret_value = mp_obj_exception_get_value(*ret_value); + } + + if (throw_value != MP_OBJ_NULL && mp_obj_exception_match(throw_value, MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { + mp_raise_o(mp_make_raise_obj(throw_value)); + return false; // caller must also check active_exception + } + return false; +} + +#else STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value) { mp_vm_return_kind_t ret_kind; nlr_buf_t nlr_buf; @@ -226,6 +302,7 @@ STATIC bool mp_native_yield_from(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *re return false; } +#endif #if !MICROPY_PY_BUILTINS_FLOAT @@ -292,6 +369,11 @@ const mp_fun_table_t mp_fun_table = { #endif nlr_pop, mp_native_raise, +#if NO_NLR + mp_native_is_exc, + mp_native_get_exc, + mp_native_clr_exc, +#endif mp_import_name, mp_import_from, mp_import_all, diff --git a/py/nativeglue.h b/py/nativeglue.h index 9d9a97b9e..47714fc9c 100644 --- a/py/nativeglue.h +++ b/py/nativeglue.h @@ -30,7 +30,9 @@ #include "py/obj.h" #include "py/persistentcode.h" #include "py/stream.h" - +#if NO_NLR +#pragma message "these must correspond to the respective enum in nativeglue.h" +#endif typedef enum { MP_F_CONST_NONE_OBJ = 0, MP_F_CONST_FALSE_OBJ, @@ -67,6 +69,11 @@ typedef enum { MP_F_NLR_PUSH, MP_F_NLR_POP, MP_F_NATIVE_RAISE, +#if NO_NLR + MP_F_NATIVE_IS_EXC, + MP_F_NATIVE_GET_EXC, + MP_F_NATIVE_CLR_EXC, +#endif MP_F_IMPORT_NAME, MP_F_IMPORT_FROM, MP_F_IMPORT_ALL, @@ -121,6 +128,11 @@ typedef struct _mp_fun_table_t { unsigned int (*nlr_push)(nlr_buf_t *); void (*nlr_pop)(void); void (*raise)(mp_obj_t o); +#if NO_NLR + mp_obj_t (*mp_native_is_exc)(void); + mp_obj_t (*mp_native_get_exc)(void); + void (*mp_native_clr_exc)(void); +#endif mp_obj_t (*import_name)(qstr name, mp_obj_t fromlist, mp_obj_t level); mp_obj_t (*import_from)(mp_obj_t module, qstr name); void (*import_all)(mp_obj_t module); @@ -135,7 +147,7 @@ typedef struct _mp_fun_table_t { mp_int_t (*small_int_floor_divide)(mp_int_t num, mp_int_t denom); mp_int_t (*small_int_modulo)(mp_int_t dividend, mp_int_t divisor); bool (*yield_from)(mp_obj_t gen, mp_obj_t send_value, mp_obj_t *ret_value); - void *setjmp_; + void *setjmp; // Additional entries for dynamic runtime, starts at index 50 void *(*memset_)(void *s, int c, size_t n); void *(*memmove_)(void *dest, const void *src, size_t n); diff --git a/py/nlr.h b/py/nlr.h index f9fbf56e5..6b285c450 100644 --- a/py/nlr.h +++ b/py/nlr.h @@ -46,43 +46,53 @@ // If MICROPY_NLR_SETJMP is not enabled then auto-detect the machine arch #if !MICROPY_NLR_SETJMP -// A lot of nlr-related things need different treatment on Windows -#if defined(_WIN32) || defined(__CYGWIN__) -#define MICROPY_NLR_OS_WINDOWS 1 -#else -#define MICROPY_NLR_OS_WINDOWS 0 -#endif -#if defined(__i386__) - #define MICROPY_NLR_X86 (1) - #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_X86) -#elif defined(__x86_64__) - #define MICROPY_NLR_X64 (1) - #if MICROPY_NLR_OS_WINDOWS - #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_X64_WIN) + + // A lot of nlr-related things need different treatment on Windows + #if defined(_WIN32) || defined(__CYGWIN__) + #define MICROPY_NLR_OS_WINDOWS 1 #else - #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_X64) + #define MICROPY_NLR_OS_WINDOWS 0 #endif -#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) - #define MICROPY_NLR_THUMB (1) - #if defined(__SOFTFP__) - #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_ARM_THUMB) + + #if defined(__i386__) + #define MICROPY_NLR_X86 (1) + #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_X86) + #elif defined(__x86_64__) + #define MICROPY_NLR_X64 (1) + #if MICROPY_NLR_OS_WINDOWS + #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_X64_WIN) + #else + #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_X64) + #endif + #elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__) + #define MICROPY_NLR_THUMB (1) + #if defined(__SOFTFP__) + #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_ARM_THUMB) + #else + // With hardware FP registers s16-s31 are callee save so in principle + // should be saved and restored by the NLR code. gcc only uses s16-s21 + // so only save/restore those as an optimisation. + #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_ARM_THUMB_FP) + #endif + + #elif defined(__xtensa__) + #define MICROPY_NLR_XTENSA (1) + #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_XTENSA) + + #elif defined(__powerpc__) + #define MICROPY_NLR_POWERPC (1) + // this could be less but using 128 for safety + #define MICROPY_NLR_NUM_REGS (128) + + #elif defined(__EMSCRIPTEN__) || defined(__WASI__) + #define MICROPY_NLR_NUM_REGS (1) + #define MICROPY_NLR_SETJMP (0) + #else - // With hardware FP registers s16-s31 are callee save so in principle - // should be saved and restored by the NLR code. gcc only uses s16-s21 - // so only save/restore those as an optimisation. - #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_ARM_THUMB_FP) + #define MICROPY_NLR_SETJMP (1) + //#warning "No native NLR support for this arch, using setjmp implementation" #endif -#elif defined(__xtensa__) - #define MICROPY_NLR_XTENSA (1) - #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_XTENSA) -#elif defined(__powerpc__) - #define MICROPY_NLR_POWERPC (1) - // this could be less but using 128 for safety - #define MICROPY_NLR_NUM_REGS (128) -#else - #define MICROPY_NLR_SETJMP (1) - //#warning "No native NLR support for this arch, using setjmp implementation" -#endif + #endif // *FORMAT-ON* diff --git a/py/obj.c b/py/obj.c index ed047acc3..ab2b56243 100644 --- a/py/obj.c +++ b/py/obj.c @@ -96,18 +96,32 @@ const mp_obj_type_t *mp_obj_get_type(mp_const_obj_t o_in) { } const char *mp_obj_get_type_str(mp_const_obj_t o_in) { + if (o_in == MP_OBJ_NULL) { + return ""; + } return qstr_str(mp_obj_get_type(o_in)->name); } void mp_obj_print_helper(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { // There can be data structures nested too deep, or just recursive +#if NO_NLR + if (o_in == MP_OBJ_NULL) { + return; + } + + if (MP_STACK_CHECK()) { + return; + } +#else MP_STACK_CHECK(); + #ifndef NDEBUG if (o_in == MP_OBJ_NULL) { mp_print_str(print, "(nil)"); return; } #endif +#endif const mp_obj_type_t *type = mp_obj_get_type(o_in); if (type->print != NULL) { type->print((mp_print_t *)print, o_in, kind); @@ -147,7 +161,7 @@ void mp_obj_print_exception(const mp_print_t *print, mp_obj_t exc) { mp_obj_print_helper(print, exc, PRINT_EXC); mp_print_str(print, "\n"); } - +#include bool mp_obj_is_true(mp_obj_t arg) { if (arg == mp_const_false) { return 0; @@ -162,6 +176,10 @@ bool mp_obj_is_true(mp_obj_t arg) { return 1; } } else { +if(!arg) { + fprintf(stderr,"180:NPE arg=%p\n",arg); + return 0; +} const mp_obj_type_t *type = mp_obj_get_type(arg); if (type->unary_op != NULL) { mp_obj_t result = type->unary_op(MP_UNARY_OP_BOOL, arg); @@ -301,6 +319,12 @@ mp_int_t mp_obj_get_int(mp_const_obj_t arg) { return mp_obj_int_get_checked(arg); } else { mp_obj_t res = mp_unary_op(MP_UNARY_OP_INT, (mp_obj_t)arg); +#if NO_NLR +#pragma message "TODO: caller should check for error" + if (res == MP_OBJ_NULL) { + return 0; + } +#endif return mp_obj_int_get_checked(res); } } @@ -359,17 +383,173 @@ mp_float_t mp_obj_get_float(mp_obj_t arg) { mp_float_t val; if (!mp_obj_get_float_maybe(arg, &val)) { +#if NO_NLR + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + mp_raise_TypeError(MP_ERROR_TEXT("can't convert to float")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("can't convert %s to float"), mp_obj_get_type_str(arg))); + return 0; + #endif +#else #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to float")); #else mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't convert %s to float"), mp_obj_get_type_str(arg)); #endif +#endif } return val; } +#endif + + +#if NO_NLR +// ----------------------------------------------------------------------------------------------------- +#if MICROPY_PY_BUILTINS_COMPLEX +#pragma message "NEED REVIEW: return exc on complex op and mp_obj_get_array_fixed_n not clear" +int mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { + if (arg == mp_const_false) { + *real = 0; + *imag = 0; + } else if (arg == mp_const_true) { + *real = 1; + *imag = 0; + } else if (mp_obj_is_small_int(arg)) { + *real = MP_OBJ_SMALL_INT_VALUE(arg); + *imag = 0; + #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE + } else if (mp_obj_is_type(arg, &mp_type_int)) { + *real = mp_obj_int_as_float_impl(arg); + *imag = 0; + #endif + } else if (mp_obj_is_float(arg)) { + *real = mp_obj_float_get(arg); + *imag = 0; + } else if (mp_obj_is_type(arg, &mp_type_complex)) { + mp_obj_complex_get(arg, real, imag); + } else { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError_o("can't convert to complex"); + } else { + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "can't convert %s to complex", mp_obj_get_type_str(arg))); + } + return 1; + } + return 0; +} + +bool mp_obj_get_complex_maybe(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { + if (arg == mp_const_false) { + *real = 0; + *imag = 0; + } else if (arg == mp_const_true) { + *real = 1; + *imag = 0; + } else if (mp_obj_is_small_int(arg)) { + *real = (mp_float_t)MP_OBJ_SMALL_INT_VALUE(arg); + *imag = 0; + #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE + } else if (mp_obj_is_type(arg, &mp_type_int)) { + *real = mp_obj_int_as_float_impl(arg); + *imag = 0; + #endif + } else if (mp_obj_is_float(arg)) { + *real = mp_obj_float_get(arg); + *imag = 0; + } else if (mp_obj_is_type(arg, &mp_type_complex)) { + mp_obj_complex_get(arg, real, imag); + } else { + return false; + } + return true; +} + +#endif // MICROPY_PY_BUILTINS_COMPLEX + +// note: returned value in *items may point to the interior of a GC block +int mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items) { + if (mp_obj_is_type(o, &mp_type_tuple)) { + mp_obj_tuple_get(o, len, items); + return 0; + } else if (mp_obj_is_type(o, &mp_type_list)) { + mp_obj_list_get(o, len, items); + return 0; + } else { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_TypeError(MP_ERROR_TEXT("expected tuple/list")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("object '%s' isn't a tuple or list"), mp_obj_get_type_str(o))); + #endif + return 1; + } +} +// note: returned value in *items may point to the interior of a GC block +void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items) { + size_t seq_len; + mp_obj_get_array(o, &seq_len, items); + if (seq_len != len) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_ValueError(MP_ERROR_TEXT("tuple/list has wrong length")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("requested length %d but object has length %d"), (int)len, (int)seq_len) ); + #endif + } +} + +// is_slice determines whether the index is a slice index +size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool is_slice) { + mp_int_t i; + if (mp_obj_is_small_int(index)) { + i = MP_OBJ_SMALL_INT_VALUE(index); + } else if (!mp_obj_get_int_maybe(index, &i)) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_TypeError(MP_ERROR_TEXT("indices must be integers")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("%q indices must be integers, not %s"), + type->name, mp_obj_get_type_str(index))); + #endif + return (size_t)-1; + } + + if (i < 0) { + i += len; + } + if (is_slice) { + if (i < 0) { + i = 0; + } else if ((mp_uint_t)i > len) { + i = len; + } + } else { + if (i < 0 || (mp_uint_t)i >= len) { + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE + #error "macro returns!" + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("index out of range")); + #else + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_IndexError, + MP_ERROR_TEXT("%q index out of range"), type->name)); + #endif + return (size_t)-1; + } + } + + // By this point 0 <= i <= len and so fits in a size_t + return (size_t)i; +} + +#else +// ================================================================================================================= #if MICROPY_PY_BUILTINS_COMPLEX bool mp_obj_get_complex_maybe(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { if (arg == mp_const_false) { @@ -407,8 +587,7 @@ void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) { #endif } } -#endif -#endif +#endif // MICROPY_PY_BUILTINS_COMPLEX // note: returned value in *items may point to the interior of a GC block void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items) { @@ -477,6 +656,8 @@ size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool // By this point 0 <= i <= len and so fits in a size_t return (size_t)i; } +// ================================================================================================================= +#endif // NO_NLR mp_obj_t mp_obj_id(mp_obj_t o_in) { mp_int_t id = (mp_int_t)o_in; @@ -501,10 +682,10 @@ mp_obj_t mp_obj_len(mp_obj_t o_in) { mp_obj_t len = mp_obj_len_maybe(o_in); if (len == MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("object has no len")); + mp_raise_TypeError(MP_ERROR_TEXT("object has no len")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("object of type '%s' has no len()"), mp_obj_get_type_str(o_in)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("object of type '%s' has no len()"), mp_obj_get_type_str(o_in))); #endif } else { return len; @@ -535,6 +716,14 @@ mp_obj_t mp_obj_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) { const mp_obj_type_t *type = mp_obj_get_type(base); if (type->subscr != NULL) { mp_obj_t ret = type->subscr(base, index, value); + +#if NO_NLR + // MP_OBJ_NULL return can mean either unsupported or exception + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#pragma message "TODO: call base classes here ?" +#endif // NO_NLR if (ret != MP_OBJ_NULL) { return ret; } @@ -542,24 +731,24 @@ mp_obj_t mp_obj_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) { } if (value == MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item deletion")); + mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item deletion")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object doesn't support item deletion"), mp_obj_get_type_str(base)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object doesn't support item deletion"), mp_obj_get_type_str(base))); #endif } else if (value == MP_OBJ_SENTINEL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("object isn't subscriptable")); + mp_raise_TypeError(MP_ERROR_TEXT("object isn't subscriptable")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object isn't subscriptable"), mp_obj_get_type_str(base)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't subscriptable"), mp_obj_get_type_str(base))); #endif } else { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("object doesn't support item assignment")); + mp_raise_TypeError_o(MP_ERROR_TEXT("object doesn't support item assignment")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object doesn't support item assignment"), mp_obj_get_type_str(base)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object doesn't support item assignment"), mp_obj_get_type_str(base))); #endif } } @@ -588,11 +777,21 @@ bool mp_get_buffer(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { return true; } +#if NO_NLR +bool mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + if (!mp_get_buffer(obj, bufinfo, flags)) { + mp_raise_TypeError_o("object with buffer protocol required"); + return false; + } + return true; +} +#else void mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags) { if (!mp_get_buffer(obj, bufinfo, flags)) { mp_raise_TypeError(MP_ERROR_TEXT("object with buffer protocol required")); } } +#endif // NO_NLR mp_obj_t mp_generic_unary_op(mp_unary_op_t op, mp_obj_t o_in) { switch (op) { diff --git a/py/obj.h b/py/obj.h index 31542a7f9..58e9e6d2f 100644 --- a/py/obj.h +++ b/py/obj.h @@ -328,7 +328,31 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t; #define MP_OBJ_FUN_ARGS_MAX (0xffff) // to set maximum value in n_args_max below #define MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, takes_kw) ((uint32_t)((((uint32_t)(n_args_min)) << 17) | (((uint32_t)(n_args_max)) << 1) | ((takes_kw) ? 1 : 0))) - +#if MICROPY_PY_FUNCTION_ATTRS +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_0}, .fun._0 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_1}, .fun._1 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_2(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_2}, .fun._2 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_3(obj_name, fun_name) \ + const mp_obj_fun_builtin_fixed_t obj_name = \ + {{&mp_type_fun_builtin_3}, .fun._3 = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_VAR(obj_name, n_args_min, fun_name) \ + const mp_obj_fun_builtin_var_t obj_name = \ + {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, false), .fun.var = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) \ + const mp_obj_fun_builtin_var_t obj_name = \ + {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, false), .fun.var = fun_name, .name = TOSTRING(fun_name)} +#define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \ + const mp_obj_fun_builtin_var_t obj_name = \ + {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name, .name = TOSTRING(fun_name)} +#else #define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ {{&mp_type_fun_builtin_0}, .fun._0 = fun_name} @@ -350,7 +374,7 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t; #define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name} - +#endif // These macros are used to define constant map/dict objects // You can put "static" in front of the definition to make it local @@ -422,8 +446,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; @@ -523,7 +545,12 @@ typedef struct _mp_buffer_p_t { mp_int_t (*get_buffer)(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags); } mp_buffer_p_t; bool mp_get_buffer(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags); -void mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags); +#if NO_NLR +bool +#else +void +#endif +mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags); struct _mp_obj_type_t { // A type is an object so must start with this entry, which points to mp_type_type. @@ -663,6 +690,9 @@ extern const mp_obj_type_t mp_type_StopAsyncIteration; extern const mp_obj_type_t mp_type_StopIteration; extern const mp_obj_type_t mp_type_SyntaxError; extern const mp_obj_type_t mp_type_SystemExit; +#if NO_NLR +extern const mp_obj_type_t mp_type_TimeoutError; +#endif extern const mp_obj_type_t mp_type_TypeError; extern const mp_obj_type_t mp_type_UnicodeError; extern const mp_obj_type_t mp_type_ValueError; @@ -685,17 +715,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 @@ -781,10 +816,22 @@ bool mp_obj_get_int_maybe(mp_const_obj_t arg, mp_int_t *value); #if MICROPY_PY_BUILTINS_FLOAT mp_float_t mp_obj_get_float(mp_obj_t self_in); bool mp_obj_get_float_maybe(mp_obj_t arg, mp_float_t *value); +#if MICROPY_PY_BUILTINS_COMPLEX +#if NO_NLR +int mp_obj_get_complex(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); +#else void mp_obj_get_complex(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); +#endif // NO_NLR bool mp_obj_get_complex_maybe(mp_obj_t self_in, mp_float_t *real, mp_float_t *imag); -#endif +#else + #error wtf +#endif //MICROPY_PY_BUILTINS_COMPLEX +#endif //MICROPY_PY_BUILTINS_FLOAT +#if NO_NLR +int mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items); // *items may point inside a GC block +#else void mp_obj_get_array(mp_obj_t o, size_t *len, mp_obj_t **items); // *items may point inside a GC block +#endif void mp_obj_get_array_fixed_n(mp_obj_t o, size_t len, mp_obj_t **items); // *items may point inside a GC block size_t mp_get_index(const mp_obj_type_t *type, size_t len, mp_obj_t index, bool is_slice); mp_obj_t mp_obj_id(mp_obj_t o_in); @@ -924,12 +971,16 @@ typedef struct _mp_obj_slice_t { mp_obj_t stop; mp_obj_t step; } mp_obj_slice_t; + void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result); // functions typedef struct _mp_obj_fun_builtin_fixed_t { mp_obj_base_t base; +#if MICROPY_PY_FUNCTION_ATTRS + const char *name; //PMPP +#endif union { mp_fun_0_t _0; mp_fun_1_t _1; @@ -941,6 +992,9 @@ typedef struct _mp_obj_fun_builtin_fixed_t { typedef struct _mp_obj_fun_builtin_var_t { mp_obj_base_t base; uint32_t sig; // see MP_OBJ_FUN_MAKE_SIG +#if MICROPY_PY_FUNCTION_ATTRS + const char *name; //PMPP +#endif union { mp_fun_var_t var; mp_fun_kw_t kw; @@ -983,7 +1037,12 @@ const mp_obj_t *mp_obj_property_get(mp_obj_t self_in); void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times, void *dest); #if MICROPY_PY_BUILTINS_SLICE -bool mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes); +#if NO_NLR +int +#else +bool +#endif +mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes); #endif #define mp_seq_copy(dest, src, len, item_t) memcpy(dest, src, len * sizeof(item_t)) #define mp_seq_cat(dest, src1, len1, src2, len2, item_t) { memcpy(dest, src1, (len1) * sizeof(item_t)); memcpy(dest + (len1), src2, (len2) * sizeof(item_t)); } diff --git a/py/objarray.c b/py/objarray.c index 4947f7590..57ef7b6c0 100644 --- a/py/objarray.c +++ b/py/objarray.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong File use _no_nlr.c instead" +#endif #include #include #include diff --git a/py/objarray.h b/py/objarray.h index 2fb6e2c91..94c31c969 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 diff --git a/py/objdeque.c b/py/objdeque.c index c95bdeee9..55c01efeb 100644 --- a/py/objdeque.c +++ b/py/objdeque.c @@ -43,7 +43,13 @@ typedef struct _mp_obj_deque_t { } mp_obj_deque_t; STATIC mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +#if NO_NLR + if (mp_arg_check_num(n_args, n_kw, 2, 3, false)) { + return MP_OBJ_NULL; + } +#else mp_arg_check_num(n_args, n_kw, 2, 3, false); +#endif /* Initialization from existing sequence is not supported, so an empty tuple must be passed as such. */ @@ -102,7 +108,7 @@ STATIC mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg) { } if (self->flags & FLAG_CHECK_OVERFLOW && new_i_put == self->i_get) { - mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("full")); + mp_raise_msg(&mp_type_IndexError, "full"); } self->items[self->i_put] = arg; @@ -122,7 +128,7 @@ STATIC mp_obj_t deque_popleft(mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); if (self->i_get == self->i_put) { - mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty")); + mp_raise_msg(&mp_type_IndexError, "empty"); } mp_obj_t ret = self->items[self->i_get]; diff --git a/py/objdict.c b/py/objdict.c index 4fa59f463..b2c14e8e5 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -24,15 +24,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "wrong file: use _no_nlr.c file" +#endif #include #include #include "py/runtime.h" #include "py/builtin.h" +#include "py/obj.h" #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 @@ -210,7 +226,11 @@ STATIC mp_obj_t dict_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { STATIC void mp_ensure_not_fixed(const mp_obj_dict_t *dict) { if (dict->map.is_fixed) { +#if NO_NLR + mp_raise_o( mp_obj_new_exception(&mp_type_TypeError)); +#else mp_raise_TypeError(NULL); +#endif } } diff --git a/py/objenumerate.c b/py/objenumerate.c index d1de4add4..0f5266c13 100644 --- a/py/objenumerate.c +++ b/py/objenumerate.c @@ -50,9 +50,15 @@ STATIC mp_obj_t enumerate_make_new(const mp_obj_type_t *type, size_t n_args, siz struct { mp_arg_val_t iterable, start; } arg_vals; - mp_arg_parse_all_kw_array(n_args, n_kw, args, - MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals); - +#if NO_NLR + if (mp_arg_parse_all_kw_array(n_args, n_kw, args, + MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals)) { + return MP_OBJ_NULL; + } +#else + mp_arg_parse_all_kw_array(n_args, n_kw, args, + MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&arg_vals); +#endif // create enumerate object mp_obj_enumerate_t *o = m_new_obj(mp_obj_enumerate_t); o->base.type = type; @@ -81,6 +87,11 @@ STATIC mp_obj_t enumerate_iternext(mp_obj_t self_in) { assert(mp_obj_is_type(self_in, &mp_type_enumerate)); mp_obj_enumerate_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t next = mp_iternext(self->iter); +#if NO_NLR + if (next == MP_OBJ_NULL) { + return MP_OBJ_NULL; + } +#endif if (next == MP_OBJ_STOP_ITERATION) { return MP_OBJ_STOP_ITERATION; } else { diff --git a/py/objexcept.c b/py/objexcept.c index 517427ed7..0baebb9f6 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); } @@ -439,7 +439,7 @@ STATIC void exc_add_strn(void *data, const char *str, size_t len) { memcpy(pr->buf + pr->len, str, len); pr->len += len; } - +mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, va_list args); mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...) { va_list args; va_start(args, fmt); @@ -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); diff --git a/py/objfilter.c b/py/objfilter.c index 41b2a3bc5..25125cf46 100644 --- a/py/objfilter.c +++ b/py/objfilter.c @@ -47,7 +47,11 @@ STATIC mp_obj_t filter_iternext(mp_obj_t self_in) { mp_check_self(mp_obj_is_type(self_in, &mp_type_filter)); mp_obj_filter_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t next; +#if NO_NLR + while ((next = mp_iternext2(self->iter)) != MP_OBJ_NULL) { +#else while ((next = mp_iternext(self->iter)) != MP_OBJ_STOP_ITERATION) { +#endif mp_obj_t val; if (self->fun != mp_const_none) { val = mp_call_function_n_kw(self->fun, 1, 0, &next); @@ -58,6 +62,11 @@ STATIC mp_obj_t filter_iternext(mp_obj_t self_in) { return next; } } +#if NO_NLR + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_STOP_ITERATION; } diff --git a/py/objfloat.c b/py/objfloat.c index 451609492..d00ba07d7 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -52,8 +52,13 @@ typedef struct _mp_obj_float_t { mp_float_t value; } mp_obj_float_t; +#if NO_NLR +const mp_obj_float_t mp_const_float_e_obj = {{&mp_type_float}, M_E}; +const mp_obj_float_t mp_const_float_pi_obj = {{&mp_type_float}, M_PI}; +#else const mp_obj_float_t mp_const_float_e_obj = {{&mp_type_float}, (mp_float_t)M_E}; const mp_obj_float_t mp_const_float_pi_obj = {{&mp_type_float}, (mp_float_t)M_PI}; +#endif #endif @@ -77,7 +82,7 @@ mp_int_t mp_float_hash(mp_float_t src) { // number may have a fraction; xor the integer part with the fractional part val = (frc >> (MP_FLOAT_FRAC_BITS - adj_exp)) ^ (frc & (((mp_float_uint_t)1 << (MP_FLOAT_FRAC_BITS - adj_exp)) - 1)); - } else if ((unsigned int)adj_exp < BITS_PER_BYTE * sizeof(mp_int_t) - 1) { + } else if ((unsigned int)adj_exp < MP_BITS_PER_BYTE * sizeof(mp_int_t) - 1) { // the number is a (big) whole integer and will fit in val's signed-width val = (mp_int_t)frc << (adj_exp - MP_FLOAT_FRAC_BITS); } else { @@ -118,8 +123,13 @@ STATIC void float_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t STATIC mp_obj_t float_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; +#if NO_NLR + if (mp_arg_check_num(n_args, n_kw, 0, 1, false)) { + return MP_OBJ_NULL; + } +#else mp_arg_check_num(n_args, n_kw, 0, 1, false); - +#endif switch (n_args) { case 0: return mp_obj_new_float(0); @@ -135,7 +145,15 @@ STATIC mp_obj_t float_make_new(const mp_obj_type_t *type_in, size_t n_args, size return args[0]; } else { // something else, try to cast it to a float +#if NO_NLR + mp_float_t val = mp_obj_get_float(args[0]); + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } + return mp_obj_new_float(val); +#else return mp_obj_new_float(mp_obj_get_float(args[0])); +#endif } } } diff --git a/py/objfun.c b/py/objfun.c index 052f4b1ce..a03f2fc6d 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include @@ -355,6 +357,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/py/objfun.h b/py/objfun.h index 905b5dbca..9c3c2bb17 100644 --- a/py/objfun.h +++ b/py/objfun.h @@ -46,4 +46,5 @@ typedef struct _mp_obj_fun_bc_t { void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest); + #endif // MICROPY_INCLUDED_PY_OBJFUN_H diff --git a/py/objgenerator.c b/py/objgenerator.c index 543685fac..f604d5267 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -85,7 +85,7 @@ const mp_obj_type_t mp_type_gen_wrap = { /******************************************************************************/ // native generator wrapper -#if MICROPY_EMIT_NATIVE +#if MICROPY_EMIT_NATIVE || MICROPY_EMIT_WASM STATIC mp_obj_t native_gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { // The state for a native generating function is held in the same struct as a bytecode function @@ -148,7 +148,15 @@ STATIC void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_pri } mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { +#if NO_NLR + if (MP_STACK_CHECK()) { + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; + } +#else MP_STACK_CHECK(); +#endif mp_check_self(mp_obj_is_type(self_in, &mp_type_gen_instance)); mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->code_state.ip == 0) { @@ -159,7 +167,15 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ // Ensure the generator cannot be reentered during execution if (self->pend_exc == MP_OBJ_NULL) { + +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("generator already executing")); + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; +#else mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); +#endif } #if MICROPY_PY_GENERATOR_PEND_THROW @@ -172,7 +188,14 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ // If the generator is started, allow sending a value. if (self->code_state.sp == self->code_state.state - 1) { if (send_value != mp_const_none) { - mp_raise_TypeError(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); +#if NO_NLR + mp_raise_TypeError_o(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); + *ret_val = MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)); + MP_STATE_THREAD(active_exception) = NULL; + return MP_VM_RETURN_EXCEPTION; +#else + mp_raise_TypeError(MP_ERROR_TEXT("can't send non-None value to a just-started generator")); +#endif } } else { *self->code_state.sp = send_value; @@ -224,7 +247,21 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ case MP_VM_RETURN_EXCEPTION: { self->code_state.ip = 0; +#if NO_NLR + // TODO probably makes sense to have bytecode also place exception in active_exception + #if MICROPY_EMIT_NATIVE + if (self->code_state.exc_sp_idx == MP_CODE_STATE_EXC_SP_IDX_SENTINEL) { + *ret_val = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; // clear it + } else + #endif + { + *ret_val = self->code_state.state[0]; + } +#else *ret_val = self->code_state.state[0]; +#endif + // PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(*ret_val)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { *ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator raised StopIteration")); @@ -245,25 +282,41 @@ STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_o if (ret == mp_const_none || ret == MP_OBJ_STOP_ITERATION) { return MP_OBJ_STOP_ITERATION; } else { +#if NO_NLR + return mp_raise_o(mp_obj_new_exception_args(&mp_type_StopIteration, 1, &ret)); +#else nlr_raise(mp_obj_new_exception_arg1(&mp_type_StopIteration, ret)); +#endif } case MP_VM_RETURN_YIELD: return ret; case MP_VM_RETURN_EXCEPTION: +#if NO_NLR + return mp_raise_o(ret); +#else nlr_raise(ret); +#endif } } -STATIC mp_obj_t gen_instance_iternext(mp_obj_t self_in) { +#if __EMSCRIPTEN__ +#else +STATIC +#endif +mp_obj_t gen_instance_iternext(mp_obj_t self_in) { return gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL); } STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) { mp_obj_t ret = gen_resume_and_raise(self_in, send_value, MP_OBJ_NULL); if (ret == MP_OBJ_STOP_ITERATION) { +#if NO_NLR + return mp_raise_o(mp_obj_new_exception(&mp_type_StopIteration)); +#else mp_raise_type(&mp_type_StopIteration); +#endif } else { return ret; } @@ -290,7 +343,11 @@ STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) { mp_obj_t ret = gen_resume_and_raise(args[0], mp_const_none, exc); if (ret == MP_OBJ_STOP_ITERATION) { +#if NO_NLR + return mp_raise_o(mp_obj_new_exception(&mp_type_StopIteration)); +#else mp_raise_type(&mp_type_StopIteration); +#endif } else { return ret; } @@ -301,7 +358,11 @@ STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) { mp_obj_t ret; switch (mp_obj_gen_resume(self_in, mp_const_none, MP_OBJ_FROM_PTR(&mp_const_GeneratorExit_obj), &ret)) { case MP_VM_RETURN_YIELD: +#if NO_NLR + return mp_raise_msg_o(&mp_type_RuntimeError, MP_ERROR_TEXT("generator ignored GeneratorExit")); +#else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator ignored GeneratorExit")); +#endif // Swallow GeneratorExit (== successful close), and re-raise any other case MP_VM_RETURN_EXCEPTION: @@ -309,7 +370,11 @@ STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) { if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) { return mp_const_none; } +#if NO_NLR + return mp_raise_o(ret); +#else nlr_raise(ret); +#endif default: // The only choice left is MP_VM_RETURN_NORMAL which is successful close @@ -322,7 +387,12 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_close_obj, gen_instance_close); STATIC mp_obj_t gen_instance_pend_throw(mp_obj_t self_in, mp_obj_t exc_in) { mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->pend_exc == MP_OBJ_NULL) { +#if NO_NLR +#pragma message "TODO: ValueError or TypeError ?" + return mp_raise_TypeError_o("can't pend throw to just-started generator"); +#else mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); +#endif } mp_obj_t prev = self->pend_exc; self->pend_exc = exc_in; diff --git a/py/objgetitemiter.c b/py/objgetitemiter.c index 54e27b8f1..835154549 100644 --- a/py/objgetitemiter.c +++ b/py/objgetitemiter.c @@ -34,7 +34,27 @@ typedef struct _mp_obj_getitem_iter_t { mp_obj_base_t base; mp_obj_t args[3]; } mp_obj_getitem_iter_t; - +#if NO_NLR +STATIC mp_obj_t it_iternext(mp_obj_t self_in) { + mp_obj_getitem_iter_t *self = MP_OBJ_TO_PTR(self_in); + // try to get next item + mp_obj_t value = mp_call_method_n_kw(1, 0, self->args); + if (value == MP_OBJ_NULL) { + // an exception was raised + mp_obj_type_t *t = (mp_obj_type_t *)MP_STATE_THREAD(active_exception)->type; + if (t == &mp_type_StopIteration || t == &mp_type_IndexError) { + // return MP_OBJ_STOP_ITERATION instead of raising + MP_STATE_THREAD(active_exception) = NULL; + return MP_OBJ_STOP_ITERATION; + } else { + // re-raise exception + return MP_OBJ_NULL; + } + } + self->args[2] = MP_OBJ_NEW_SMALL_INT(MP_OBJ_SMALL_INT_VALUE(self->args[2]) + 1); + return value; +} +#else STATIC mp_obj_t it_iternext(mp_obj_t self_in) { mp_obj_getitem_iter_t *self = MP_OBJ_TO_PTR(self_in); nlr_buf_t nlr; @@ -56,6 +76,7 @@ STATIC mp_obj_t it_iternext(mp_obj_t self_in) { } } } +#endif STATIC const mp_obj_type_t it_type = { { &mp_type_type }, diff --git a/py/objint.c b/py/objint.c index 4619fb575..67d0f8911 100644 --- a/py/objint.c +++ b/py/objint.c @@ -38,10 +38,11 @@ #if MICROPY_PY_BUILTINS_FLOAT #include #endif - +#include // This dispatcher function is expected to be independent of the implementation of long int STATIC mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { (void)type_in; + mp_arg_check_num(n_args, n_kw, 0, 2, false); switch (n_args) { @@ -49,6 +50,10 @@ STATIC mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, return MP_OBJ_NEW_SMALL_INT(0); case 1: + if (!args[0]) { + fprintf(stderr,"54:mp_obj_int_make_new NPE\n"); + return MP_OBJ_NULL; + } if (mp_obj_is_int(args[0])) { // already an int (small or long), just return it return args[0]; @@ -67,8 +72,13 @@ STATIC mp_obj_t mp_obj_int_make_new(const mp_obj_type_t *type_in, size_t n_args, case 2: default: { + if (!args[0]) { + fprintf(stderr,"76:mp_obj_int_make_new NPE\n"); + return MP_OBJ_NULL; + } // should be a string, parse it size_t l; + fprintf(stderr,"72:mp_obj_int_make_new %p\n", args[0] ); const char *s = mp_obj_str_get_data(args[0], &l); return mp_parse_num_integer(s, l, mp_obj_get_int(args[1]), NULL); } @@ -121,7 +131,7 @@ STATIC mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { return MP_FP_CLASS_FIT_SMALLINT; } #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG - if (e <= (((sizeof(long long) * BITS_PER_BYTE) + MP_FLOAT_EXP_BIAS - 2) << MP_FLOAT_EXP_SHIFT_I32)) { + if (e <= (((sizeof(long long) * MP_BITS_PER_BYTE) + MP_FLOAT_EXP_BIAS - 2) << MP_FLOAT_EXP_SHIFT_I32)) { return MP_FP_CLASS_FIT_LONGINT; } #endif @@ -134,6 +144,36 @@ STATIC mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { #undef MP_FLOAT_SIGN_SHIFT_I32 #undef MP_FLOAT_EXP_SHIFT_I32 +#if NO_NLR +mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { + + int cl = fpclassify((float)val); + if (cl == FP_INFINITE) { + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("can't convert inf to int")); + } else if (cl == FP_NAN) { + mp_raise_ValueError(MP_ERROR_TEXT("can't convert NaN to int")); + } else { + mp_fp_as_int_class_t icl = mp_classify_fp_as_int(val); + if (icl == MP_FP_CLASS_FIT_SMALLINT) { + return MP_OBJ_NEW_SMALL_INT((mp_int_t)val); + #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_MPZ + } else { + mp_obj_int_t *o = mp_obj_int_new_mpz(); + mpz_set_from_float(&o->mpz, val); + return MP_OBJ_FROM_PTR(o); + } + #else + #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG + } else if (icl == MP_FP_CLASS_FIT_LONGINT) { + return mp_obj_new_int_from_ll((long long)val); + #endif + } else { + mp_raise_ValueError(MP_ERROR_TEXT("float too big")); + } + #endif + } +} +#else // NO_NLR mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { mp_float_union_t u = {val}; // IEEE-754: if biased exponent is all 1 bits... @@ -165,6 +205,7 @@ mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { #endif } } +#endif // NO_NLR #endif diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 6e52073a6..120a80d59 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); } @@ -275,6 +274,11 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i case MP_BINARY_OP_RSHIFT: case MP_BINARY_OP_INPLACE_RSHIFT: { mp_int_t irhs = mp_obj_int_get_checked(rhs_in); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif if (irhs < 0) { mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } @@ -416,6 +420,9 @@ mp_int_t mp_obj_int_get_truncated(mp_const_obj_t self_in) { } } +#if NO_NLR +#pragma message "TODO callers must handle exceptions mp_obj_(u)int_get_checked" +#endif mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { return MP_OBJ_SMALL_INT_VALUE(self_in); @@ -426,7 +433,13 @@ mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { return value; } else { // overflow +#if NO_NLR + mp_raise_msg_o(&mp_type_OverflowError, MP_ERROR_TEXT("438:overflow converting long int to machine word")); + return 0; // TODO callers must handle exceptions +#else mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); +#endif + } } } @@ -444,7 +457,12 @@ mp_uint_t mp_obj_int_get_uint_checked(mp_const_obj_t self_in) { } } +#if NO_NLR + mp_raise_msg_o(&mp_type_OverflowError, MP_ERROR_TEXT("462:overflow converting long int to machine word")); + return 0; // TODO callers must handle exceptions +#else mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("overflow converting long int to machine word")); +#endif } #if MICROPY_PY_BUILTINS_FLOAT diff --git a/py/objlist.c b/py/objlist.c index 8c989facc..69424c479 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -23,6 +23,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include diff --git a/py/objlist.h b/py/objlist.h index a43663db7..5a01dd650 100644 --- a/py/objlist.h +++ b/py/objlist.h @@ -35,6 +35,10 @@ typedef struct _mp_obj_list_t { mp_obj_t *items; } mp_obj_list_t; +#if NO_NLR +mp_obj_list_t *mp_obj_list_init(mp_obj_list_t *o, size_t n); +#else void mp_obj_list_init(mp_obj_list_t *o, size_t n); +#endif #endif // MICROPY_INCLUDED_PY_OBJLIST_H diff --git a/py/objmap.c b/py/objmap.c index 78c52c892..557bfe684 100644 --- a/py/objmap.c +++ b/py/objmap.c @@ -59,6 +59,12 @@ STATIC mp_obj_t map_iternext(mp_obj_t self_in) { m_del(mp_obj_t, nextses, self->n_iters); return MP_OBJ_STOP_ITERATION; } +#if NO_NLR + if (next == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } +#endif nextses[i] = next; } return mp_call_function_n_kw(self->fun, self->n_iters, 0, nextses); diff --git a/py/objnamedtuple.c b/py/objnamedtuple.c index e4543f5b9..e0b18d3c6 100644 --- a/py/objnamedtuple.c +++ b/py/objnamedtuple.c @@ -87,7 +87,11 @@ STATIC void namedtuple_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } else { // delete/store attribute // provide more detailed error message than we'd get by just returning +#if NO_NLR + mp_raise_o( mp_obj_new_exception_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't set attribute")) ); +#else mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't set attribute")); +#endif } } @@ -97,14 +101,17 @@ STATIC mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, if (n_args + n_kw != num_fields) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); +#if NO_NLR + return MP_OBJ_NULL; +#endif #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL - mp_raise_msg_varg(&mp_type_TypeError, + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("function takes %d positional arguments but %d were given"), - num_fields, n_args + n_kw); + num_fields, n_args + n_kw)); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_DETAILED - mp_raise_msg_varg(&mp_type_TypeError, + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("%q() takes %d positional arguments but %d were given"), - type->base.name, num_fields, n_args + n_kw); + type->base.name, num_fields, n_args + n_kw)); #endif } @@ -123,16 +130,21 @@ STATIC mp_obj_t namedtuple_make_new(const mp_obj_type_t *type_in, size_t n_args, if (id == (size_t)-1) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_arg_error_terse_mismatch(); +#if NO_NLR + return MP_OBJ_NULL; +#endif #else - mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("unexpected keyword argument '%q'"), kw); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("unexpected keyword argument '%q'"), kw)); #endif } if (tuple->items[id] != MP_OBJ_NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_arg_error_terse_mismatch(); + mp_arg_error_terse_mismatch(); + return MP_OBJ_NULL; #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("function got multiple values for argument '%q'"), kw); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("function got multiple values for argument '%q'"), kw)); #endif } tuple->items[id] = args[i + 1]; @@ -176,7 +188,13 @@ STATIC mp_obj_t new_namedtuple_type(mp_obj_t name_in, mp_obj_t fields_in) { fields_in = mp_obj_str_split(1, &fields_in); } #endif +#if NO_NLR + if (mp_obj_get_array(fields_in, &n_fields, &fields)) { + return MP_OBJ_NULL; + } +#else mp_obj_get_array(fields_in, &n_fields, &fields); +#endif return mp_obj_new_namedtuple_type(name, n_fields, fields); } MP_DEFINE_CONST_FUN_OBJ_2(mp_namedtuple_obj, new_namedtuple_type); diff --git a/py/objrange.c b/py/objrange.c index 4eed4b941..b7cb80678 100644 --- a/py/objrange.c +++ b/py/objrange.c @@ -90,7 +90,13 @@ STATIC void range_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind } STATIC mp_obj_t range_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +#if NO_NLR + if (mp_arg_check_num(n_args, n_kw, 1, 3, false)) { + return MP_OBJ_NULL; + } +#else mp_arg_check_num(n_args, n_kw, 1, 3, false); +#endif mp_obj_range_t *o = m_new_obj(mp_obj_range_t); o->base.type = type; @@ -110,6 +116,12 @@ STATIC mp_obj_t range_make_new(const mp_obj_type_t *type, size_t n_args, size_t } } +#if NO_NLR + // check for errors from mp_obj_get_int + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif return MP_OBJ_FROM_PTR(o); } diff --git a/py/objset.c b/py/objset.c index f31a901a7..e7df136a1 100644 --- a/py/objset.c +++ b/py/objset.c @@ -175,6 +175,11 @@ STATIC mp_obj_t set_copy(mp_obj_t self_in) { check_set_or_frozenset(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_set_t *other = m_new_obj(mp_obj_set_t); +#if NO_NLR + if (other == NULL) { + return MP_OBJ_NULL; + } +#endif other->base.type = self->base.type; mp_set_init(&other->set, self->set.alloc); other->set.used = self->set.used; @@ -372,7 +377,7 @@ STATIC mp_obj_t set_remove(mp_obj_t self_in, mp_obj_t item) { check_set(self_in); mp_obj_set_t *self = MP_OBJ_TO_PTR(self_in); if (mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == MP_OBJ_NULL) { - nlr_raise(mp_obj_new_exception_arg1(&mp_type_KeyError, item)); + mp_raise_or_return(mp_obj_new_exception_arg1(&mp_type_KeyError, item)); } return mp_const_none; } @@ -445,6 +450,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 @@ -579,6 +585,11 @@ const mp_obj_type_t mp_type_frozenset = { mp_obj_t mp_obj_new_set(size_t n_args, mp_obj_t *items) { mp_obj_set_t *o = m_new_obj(mp_obj_set_t); +#if NO_NLR + if (o == NULL) { + return MP_OBJ_NULL; + } +#endif o->base.type = &mp_type_set; mp_set_init(&o->set, n_args); for (size_t i = 0; i < n_args; i++) { diff --git a/py/objslice.c b/py/objslice.c index c65c30601..8e8800c9f 100644 --- a/py/objslice.c +++ b/py/objslice.c @@ -51,7 +51,11 @@ STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) { mp_int_t length = mp_obj_int_get_checked(length_obj); mp_bound_slice_t bound_indices; mp_obj_slice_indices(self_in, length, &bound_indices); - +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_obj_t results[3] = { MP_OBJ_NEW_SMALL_INT(bound_indices.start), MP_OBJ_NEW_SMALL_INT(bound_indices.stop), @@ -59,6 +63,7 @@ STATIC mp_obj_t slice_indices(mp_obj_t self_in, mp_obj_t length_obj) { }; return mp_obj_new_tuple(3, results); } + STATIC MP_DEFINE_CONST_FUN_OBJ_2(slice_indices_obj, slice_indices); #endif @@ -105,6 +110,11 @@ const mp_obj_type_t mp_type_slice = { mp_obj_t mp_obj_new_slice(mp_obj_t ostart, mp_obj_t ostop, mp_obj_t ostep) { mp_obj_slice_t *o = m_new_obj(mp_obj_slice_t); +#if NO_NLR + if (o == NULL) { + return MP_OBJ_NULL; + } +#endif o->base.type = &mp_type_slice; o->start = ostart; o->stop = ostop; @@ -124,7 +134,11 @@ void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *r } else { step = mp_obj_get_int(self->step); if (step == 0) { +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("slice step can't be zero")); +#else mp_raise_ValueError(MP_ERROR_TEXT("slice step can't be zero")); +#endif } } diff --git a/py/objstr.c b/py/objstr.c index a276a255e..bc66d15d9 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -24,7 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include @@ -123,10 +125,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); } @@ -205,6 +207,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; diff --git a/py/objstringio.c b/py/objstringio.c index ef942e74e..5a3b1dcc7 100644 --- a/py/objstringio.c +++ b/py/objstringio.c @@ -35,6 +35,22 @@ #if MICROPY_PY_IO +#if NO_NLR + +#if MICROPY_CPYTHON_COMPAT +STATIC int check_stringio_is_open(const mp_obj_stringio_t *o) { + if (o->vstr == NULL) { + mp_raise_ValueError_o(MP_ERROR_TEXT("I/O operation on closed file")); + return 1; + } + return 0; +} +#else +#define check_stringio_is_open(o) 0 +#endif + +#else // NO_NLR + #if MICROPY_CPYTHON_COMPAT STATIC void check_stringio_is_open(const mp_obj_stringio_t *o) { if (o->vstr == NULL) { @@ -45,6 +61,8 @@ STATIC void check_stringio_is_open(const mp_obj_stringio_t *o) { #define check_stringio_is_open(o) #endif +#endif // NO_NLR + STATIC void stringio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_stringio_t *self = MP_OBJ_TO_PTR(self_in); @@ -54,7 +72,14 @@ STATIC void stringio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k STATIC mp_uint_t stringio_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) { (void)errcode; mp_obj_stringio_t *o = MP_OBJ_TO_PTR(o_in); + +#if NO_NLR + if (check_stringio_is_open(o)) { + return MP_STREAM_ERROR; + } +#else check_stringio_is_open(o); +#endif if (o->vstr->len <= o->pos) { // read to EOF, or seeked to EOF or beyond return 0; } @@ -78,8 +103,14 @@ STATIC void stringio_copy_on_write(mp_obj_stringio_t *o) { STATIC mp_uint_t stringio_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) { (void)errcode; mp_obj_stringio_t *o = MP_OBJ_TO_PTR(o_in); - check_stringio_is_open(o); +#if NO_NLR + if (check_stringio_is_open(o)) { + return MP_STREAM_ERROR; + } +#else + check_stringio_is_open(o); +#endif if (o->vstr->fixed_buf) { stringio_copy_on_write(o); } @@ -164,8 +195,16 @@ STATIC mp_uint_t stringio_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, STATIC mp_obj_t stringio_getvalue(mp_obj_t self_in) { mp_obj_stringio_t *self = MP_OBJ_TO_PTR(self_in); +#if NO_NLR +#pragma message "TODO: Try to avoid copying string" + if (check_stringio_is_open(self)) { + return MP_OBJ_NULL; + } +#else check_stringio_is_open(self); // TODO: Try to avoid copying string +#endif + return mp_obj_new_str_of_type(STREAM_TO_CONTENT_TYPE(self), (byte *)self->vstr->buf, self->vstr->len); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(stringio_getvalue_obj, stringio_getvalue); @@ -185,8 +224,8 @@ STATIC mp_obj_stringio_t *stringio_new(const mp_obj_type_t *type) { } STATIC mp_obj_t stringio_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - (void)n_kw; // TODO check n_kw==0 - + (void)n_kw; +#pragma message "TODO: check n_kw==0" mp_uint_t sz = 16; bool initdata = false; mp_buffer_info_t bufinfo; diff --git a/py/objstrunicode.c b/py/objstrunicode.c index b21df22a3..817c17c4f 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); } @@ -129,7 +129,14 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s if (mp_obj_is_small_int(index)) { i = MP_OBJ_SMALL_INT_VALUE(index); } else if (!mp_obj_get_int_maybe(index, &i)) { - mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("string indices must be integers, not %s"), mp_obj_get_type_str(index)); +#if NO_NLR + mp_raise_o(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("string indices must be integers, not %s"), mp_obj_get_type_str(index))); + return NULL; +#else + mp_raise_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("string indices must be integers, not %s"), mp_obj_get_type_str(index)); +#endif } const byte *s, *top = self_data + self_len; if (i < 0) { @@ -139,7 +146,7 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s if (is_slice) { return self_data; } - mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); + goto excepthandler; } if (!UTF8_IS_CONT(*s)) { ++i; @@ -158,7 +165,7 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s if (is_slice) { return top; } - mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); + goto excepthandler; } // Then check completion if (i-- == 0) { @@ -172,6 +179,13 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s } } return s; +excepthandler: +#if NO_NLR + mp_raise_msg_o(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); +#else + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("string index out of range")); +#endif + return NULL; } STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { @@ -195,6 +209,10 @@ STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { const byte *pstart, *pstop; if (ostart != mp_const_none) { pstart = str_index_to_ptr(type, self_data, self_len, ostart, true); +#if NO_NLR + if (pstart==NULL) + return MP_OBJ_NULL; +#endif } else { pstart = self_data; } @@ -202,6 +220,11 @@ STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { // pstop will point just after the stop character. This depends on // the \0 at the end of the string. pstop = str_index_to_ptr(type, self_data, self_len, ostop, true); +#if NO_NLR + if (pstop==NULL) + return MP_OBJ_NULL; +#endif + } else { pstop = self_data + self_len; } @@ -212,6 +235,11 @@ STATIC mp_obj_t str_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { } #endif const byte *s = str_index_to_ptr(type, self_data, self_len, index, false); +#if NO_nLR + if (s == NULL) { + return MP_OBJ_NULL; + } +#endif int len = 1; if (UTF8_IS_NONASCII(*s)) { // Count the number of 1 bits (after the first) diff --git a/py/objtuple.c b/py/objtuple.c index 07a560ac0..98bfd9fac 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -86,7 +86,10 @@ STATIC mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_arg mp_obj_t iterable = mp_getiter(args[0], NULL); mp_obj_t item; - while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + while ((item = mp_iternext(iterable)) != MP_OBJ_NULL) { +#if NO_NLR + if ( item == MP_OBJ_STOP_ITERATION) goto done; +#endif if (len >= alloc) { items = m_renew(mp_obj_t, items, alloc, alloc * 2); alloc *= 2; @@ -94,6 +97,12 @@ STATIC mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_arg items[len++] = item; } +#if NO_NLR + if (mp_iternext_had_exc()) { + return MP_OBJ_NULL; + } +done:; +#endif mp_obj_t tuple = mp_obj_new_tuple(len, items); m_del(mp_obj_t, items, alloc); @@ -193,6 +202,12 @@ mp_obj_t mp_obj_tuple_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { } #endif size_t index_value = mp_get_index(self->base.type, self->len, index, false); +#if NO_NLR + if (index_value == (size_t)-1) { + // exception + return MP_OBJ_NULL; + } +#endif return self->items[index_value]; } else { return MP_OBJ_NULL; // op not supported @@ -240,6 +255,11 @@ mp_obj_t mp_obj_new_tuple(size_t n, const mp_obj_t *items) { return mp_const_empty_tuple; } mp_obj_tuple_t *o = m_new_obj_var(mp_obj_tuple_t, mp_obj_t, n); +#if NO_NLR + if (o == NULL) { + return MP_OBJ_NULL; + } +#endif o->base.type = &mp_type_tuple; o->len = n; if (items) { diff --git a/py/objtype.c b/py/objtype.c index 40900dc05..086e0845a 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -351,8 +351,8 @@ mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, size #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("__init__() should return None")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("__init__() should return None, not '%s'"), mp_obj_get_type_str(init_ret)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("__init__() should return None, not '%s'"), mp_obj_get_type_str(init_ret))); #endif } } @@ -423,6 +423,11 @@ STATIC mp_obj_t instance_unary_op(mp_unary_op_t op, mp_obj_t self_in) { case MP_UNARY_OP_HASH: // __hash__ must return a small int val = MP_OBJ_NEW_SMALL_INT(mp_obj_get_int_truncated(val)); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif break; case MP_UNARY_OP_INT: // Must return int @@ -624,7 +629,11 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des // the code. const mp_obj_t *proxy = mp_obj_property_get(member); if (proxy[0] == mp_const_none) { +#if NO_NLR + mp_raise_msg_o(&mp_type_AttributeError, MP_ERROR_TEXT("unreadable attribute")); +#else mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("unreadable attribute")); +#endif } else { dest[0] = mp_call_function_n_kw(proxy[0], 1, 0, &self_in); } @@ -863,8 +872,8 @@ mp_obj_t mp_obj_instance_call(mp_obj_t self_in, size_t n_args, size_t n_kw, cons #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("object not callable")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("'%s' object isn't callable"), mp_obj_get_type_str(self_in)); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("'%s' object isn't callable"), mp_obj_get_type_str(self_in))); #endif } mp_obj_instance_t *self = MP_OBJ_TO_PTR(self_in); @@ -985,11 +994,21 @@ STATIC mp_obj_t type_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in); if (self->make_new == NULL) { + // module(name[, doc]) + if (n_args) { + const char * mod_name = mp_obj_str_get_str(args[0]); + if (mod_name) { + return mp_obj_new_module( qstr_from_strn(mod_name, strlen(mod_name) )); + } + return MP_OBJ_NULL; + } else { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE - mp_raise_TypeError(MP_ERROR_TEXT("can't create instance")); + mp_raise_TypeError(MP_ERROR_TEXT("can't create instance")); #else - mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("can't create '%q' instances"), self->name); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("cannot create '%q' instances"), self->name)); #endif + } } // make new instance @@ -1014,13 +1033,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; } @@ -1072,7 +1094,14 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (check_for_special_accessors(MP_OBJ_NEW_QSTR(attr), dest[1])) { if (self->flags & MP_TYPE_FLAG_IS_SUBCLASSED) { // This class is already subclassed so can't have special accessors added - mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't add special method to already-subclassed class")); +#if NO_NLR + mp_raise_msg_o(&mp_type_AttributeError, + MP_ERROR_TEXT("can't add special method to already-subclassed class")); +#else + mp_raise_msg(&mp_type_AttributeError, + MP_ERROR_TEXT("can't add special method to already-subclassed class")); +#endif + return; } self->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } @@ -1120,13 +1149,13 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) mp_raise_TypeError(NULL); } mp_obj_type_t *t = MP_OBJ_TO_PTR(bases_items[i]); - // TODO: Verify with CPy, tested on function type +#pragma message "TODO: Verify with CPy, tested on function type" if (t->make_new == NULL) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("type isn't an acceptable base type")); #else - mp_raise_msg_varg(&mp_type_TypeError, - MP_ERROR_TEXT("type '%q' isn't an acceptable base type"), t->name); + mp_raise_or_return(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + MP_ERROR_TEXT("type '%q' isn't an acceptable base type"), t->name)); #endif } #if ENABLE_SPECIAL_ACCESSORS @@ -1312,10 +1341,17 @@ const mp_obj_type_t mp_type_super = { .attr = super_attr, }; +#if NO_NLR +mp_obj_t mp_load_super_method(qstr attr, mp_obj_t *dest) { + mp_obj_super_t super = {{&mp_type_super}, dest[1], dest[2]}; + return mp_load_method(MP_OBJ_FROM_PTR(&super), attr, dest); +} +#else void mp_load_super_method(qstr attr, mp_obj_t *dest) { mp_obj_super_t super = {{&mp_type_super}, dest[1], dest[2]}; mp_load_method(MP_OBJ_FROM_PTR(&super), attr, dest); } +#endif /******************************************************************************/ // subclassing and built-ins specific to types diff --git a/py/objzip.c b/py/objzip.c index 4abc917c3..bd3e512a6 100644 --- a/py/objzip.c +++ b/py/objzip.c @@ -58,6 +58,12 @@ STATIC mp_obj_t zip_iternext(mp_obj_t self_in) { for (size_t i = 0; i < self->n_iters; i++) { mp_obj_t next = mp_iternext(self->iters[i]); +#if NO_NLR + if (next == MP_OBJ_NULL) { + // exception + return MP_OBJ_NULL; + } +#endif if (next == MP_OBJ_STOP_ITERATION) { mp_obj_tuple_del(MP_OBJ_FROM_PTR(tuple)); return MP_OBJ_STOP_ITERATION; diff --git a/py/parse.c b/py/parse.c index 38cd215a9..4b06f2360 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 { @@ -79,8 +76,8 @@ enum { // Define an array of actions corresponding to each rule STATIC const uint8_t rule_act_table[] = { -#define or(n) (RULE_ACT_OR | n) -#define and(n) (RULE_ACT_AND | n) +#define C_or(n) (RULE_ACT_OR | n) +#define C_and(n) (RULE_ACT_AND | n) #define and_ident(n) (RULE_ACT_AND | n | RULE_ACT_ALLOW_IDENT) #define and_blank(n) (RULE_ACT_AND | n | RULE_ACT_ADD_BLANK) #define one_or_more (RULE_ACT_LIST | 2) @@ -101,8 +98,8 @@ STATIC const uint8_t rule_act_table[] = { #undef DEF_RULE #undef DEF_RULE_NC -#undef or -#undef and +#undef C_or +#undef C_and #undef and_ident #undef and_blank #undef one_or_more @@ -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, @@ -284,6 +281,11 @@ STATIC void *parser_alloc(parser_t *parser, size_t num_bytes) { alloc = num_bytes; } chunk = (mp_parse_chunk_t *)m_new(byte, sizeof(mp_parse_chunk_t) + alloc); +#if NO_NLR + if (chunk == NULL) { + return NULL; + } +#endif chunk->alloc = alloc; chunk->union_.used = 0; parser->cur_chunk = chunk; @@ -410,7 +412,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); @@ -452,7 +454,12 @@ STATIC void push_result_node(parser_t *parser, mp_parse_node_t pn) { } STATIC mp_parse_node_t make_node_const_object(parser_t *parser, size_t src_line, mp_obj_t obj) { - mp_parse_node_struct_t *pn = parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_obj_t)); + mp_parse_node_struct_t *pn = (mp_parse_node_struct_t *)parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_obj_t)); +#if NO_NLR + if (pn == NULL) { + return MP_PARSE_NODE_NULL; + } +#endif pn->source_line = src_line; #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D // nodes are 32-bit pointers, but need to store 64-bit object @@ -611,7 +618,11 @@ STATIC bool fold_logical_constants(parser_t *parser, uint8_t rule_id, size_t *nu return false; } +#if NO_NLR +STATIC int fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { +#else STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { +#endif // this code does folding of arbitrary integer expressions, eg 1 + 2 * 3 + 4 // it does not do partial folding, eg 1 + 2 + x -> 3 + x @@ -724,7 +735,7 @@ STATIC bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { MP_ERROR_TEXT("constant must be an integer")); mp_obj_exception_add_traceback(exc, parser->lexer->source_name, ((mp_parse_node_struct_t *)pn1)->source_line, MP_QSTRnull); - nlr_raise(exc); + mp_raise_or_return_value(exc, -1); } // store the value in the table of dynamic constants @@ -824,7 +835,12 @@ STATIC void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, } #endif - mp_parse_node_struct_t *pn = parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_parse_node_t) * num_args); + mp_parse_node_struct_t *pn = (mp_parse_node_struct_t *)parser_alloc(parser, sizeof(mp_parse_node_struct_t) + sizeof(mp_parse_node_t) * num_args); +#if NO_NLR + if (pn == NULL) { + return; + } +#endif pn->source_line = src_line; pn->kind_num_nodes = (rule_id & 0xff) | (num_args << 8); for (size_t i = num_args; i > 0; i--) { @@ -847,6 +863,11 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { parser.result_stack_top = 0; parser.result_stack = m_new(mp_parse_node_t, parser.result_stack_alloc); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return parser.tree; + } +#endif parser.lexer = lex; parser.tree.chunk = NULL; @@ -948,7 +969,7 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { for (; i < n; ++i) { if ((rule_arg[i] & RULE_ARG_KIND_MASK) == RULE_ARG_TOK) { // need to match a token - mp_token_kind_t tok_kind = rule_arg[i] & RULE_ARG_ARG_MASK; + mp_token_kind_t tok_kind = (mp_token_kind_t)(rule_arg[i] & RULE_ARG_ARG_MASK); if (lex->tok_kind == tok_kind) { // matched token if (tok_kind == MP_TOKEN_NAME) { @@ -1000,7 +1021,7 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { for (size_t x = n; x > 0;) { --x; if ((rule_arg[x] & RULE_ARG_KIND_MASK) == RULE_ARG_TOK) { - mp_token_kind_t tok_kind = rule_arg[x] & RULE_ARG_ARG_MASK; + mp_token_kind_t tok_kind = (mp_token_kind_t)(rule_arg[x] & RULE_ARG_ARG_MASK); if (tok_kind == MP_TOKEN_NAME) { // only tokens which were names are pushed to stack i += 1; @@ -1126,6 +1147,12 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { break; } } + +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return parser.tree; + } +#endif } #if MICROPY_COMP_CONST @@ -1162,7 +1189,7 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { // add traceback to give info about file name and location // we don't have a 'block' name, so just pass the NULL qstr to indicate this mp_obj_exception_add_traceback(exc, lex->source_name, lex->tok_line, MP_QSTRnull); - nlr_raise(exc); + mp_raise_or_return_value(exc, parser.tree); } // get the root parse node that we created diff --git a/py/parsenum.c b/py/parsenum.c index e665da7d8..2c164ee8e 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -36,14 +36,18 @@ #include #endif +#if NO_NLR +STATIC mp_obj_t raise_exc(mp_obj_t exc, mp_lexer_t *lex) { +#else STATIC NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { +#endif // if lex!=NULL then the parser called us and we need to convert the // exception's type from ValueError to SyntaxError and add traceback info if (lex != NULL) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(exc))->type = &mp_type_SyntaxError; mp_obj_exception_add_traceback(exc, lex->source_name, lex->tok_line, MP_QSTRnull); } - nlr_raise(exc); + mp_raise_or_return(exc); } mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, mp_lexer_t *lex) { @@ -148,10 +152,16 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for integer")); +#if NO_NLR + return +#endif raise_exc(exc, lex); #elif MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NORMAL mp_obj_t exc = mp_obj_new_exception_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for integer with base %d"), base); +#if NO_NLR + return +#endif raise_exc(exc, lex); #else vstr_t vstr; @@ -161,8 +171,14 @@ mp_obj_t mp_parse_num_integer(const char *restrict str_, size_t len, int base, m mp_str_print_quoted(&print, str_val_start, top - str_val_start, true); mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_ValueError, mp_obj_new_str_from_vstr(&mp_type_str, &vstr)); +#if NO_NLR +#else raise_exc(exc, lex); +#endif #endif +#if NO_NLR + return raise_exc(exc, lex); +#endif } } @@ -344,6 +360,9 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool } #else if (imag || force_complex) { +#if NO_NLR + return +#endif raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("complex values not supported")), lex); } #endif @@ -352,9 +371,15 @@ mp_obj_t mp_parse_num_decimal(const char *str, size_t len, bool allow_imag, bool } value_error: +#if NO_NLR + return +#endif raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("invalid syntax for number")), lex); #else +#if NO_NLR + return +#endif raise_exc(mp_obj_new_exception_msg(&mp_type_ValueError, MP_ERROR_TEXT("decimal numbers not supported")), lex); #endif } diff --git a/py/persistentcode.c b/py/persistentcode.c index da3234a5f..8447d649f 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -554,12 +554,22 @@ mp_raw_code_t *mp_raw_code_load(mp_reader_t *reader) { || MPY_FEATURE_DECODE_FLAGS(header[2]) != MPY_FEATURE_FLAGS || header[3] > mp_small_int_bits() || read_uint(reader, NULL) > QSTR_WINDOW_SIZE) { +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("incompatible .mpy file")); + return NULL; +#else mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy file")); +#endif } if (MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE) { byte arch = MPY_FEATURE_DECODE_ARCH(header[2]); if (!MPY_FEATURE_ARCH_TEST(arch)) { +#if NO_NLR + mp_raise_ValueError_o(MP_ERROR_TEXT("incompatible .mpy arch")); + return NULL; +#else mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy arch")); +#endif } } qstr_window_t qw; @@ -595,7 +605,7 @@ STATIC void mp_print_bytes(mp_print_t *print, const byte *data, size_t len) { print->print_strn(print->data, (const char *)data, len); } -#define BYTES_FOR_INT ((BYTES_PER_WORD * 8 + 6) / 7) +#define BYTES_FOR_INT ((MP_BYTES_PER_OBJ_WORD * 8 + 6) / 7) STATIC void mp_print_uint(mp_print_t *print, size_t n) { byte buf[BYTES_FOR_INT]; byte *p = buf + sizeof(buf); @@ -825,10 +835,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 +860,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 diff --git a/py/py.cmake b/py/py.cmake new file mode 100644 index 000000000..52baaa8ef --- /dev/null +++ b/py/py.cmake @@ -0,0 +1,124 @@ +# CMake fragment for MicroPython core py component + +set(MICROPY_PY_DIR "${MICROPY_DIR}/py") + +# All py/ source files +set(MICROPY_SOURCE_PY + ${MICROPY_PY_DIR}/argcheck.c + ${MICROPY_PY_DIR}/asmarm.c + ${MICROPY_PY_DIR}/asmbase.c + ${MICROPY_PY_DIR}/asmthumb.c + ${MICROPY_PY_DIR}/asmx64.c + ${MICROPY_PY_DIR}/asmx86.c + ${MICROPY_PY_DIR}/asmxtensa.c + ${MICROPY_PY_DIR}/bc.c + ${MICROPY_PY_DIR}/binary.c + ${MICROPY_PY_DIR}/builtinevex.c + ${MICROPY_PY_DIR}/builtinhelp.c + ${MICROPY_PY_DIR}/builtinimport.c + ${MICROPY_PY_DIR}/compile.c + ${MICROPY_PY_DIR}/emitbc.c + ${MICROPY_PY_DIR}/emitcommon.c + ${MICROPY_PY_DIR}/emitglue.c + ${MICROPY_PY_DIR}/emitinlinethumb.c + ${MICROPY_PY_DIR}/emitinlinextensa.c + ${MICROPY_PY_DIR}/emitnarm.c + ${MICROPY_PY_DIR}/emitnthumb.c + ${MICROPY_PY_DIR}/emitnx64.c + ${MICROPY_PY_DIR}/emitnx86.c + ${MICROPY_PY_DIR}/emitnxtensa.c + ${MICROPY_PY_DIR}/emitnxtensawin.c + ${MICROPY_PY_DIR}/formatfloat.c + ${MICROPY_PY_DIR}/frozenmod.c + ${MICROPY_PY_DIR}/gc.c + ${MICROPY_PY_DIR}/lexer.c + ${MICROPY_PY_DIR}/malloc.c + ${MICROPY_PY_DIR}/map.c + ${MICROPY_PY_DIR}/modarray.c + ${MICROPY_PY_DIR}/modbuiltins.c + ${MICROPY_PY_DIR}/modcmath.c + ${MICROPY_PY_DIR}/modcollections.c + ${MICROPY_PY_DIR}/modgc.c + ${MICROPY_PY_DIR}/modio.c + ${MICROPY_PY_DIR}/modmath.c + ${MICROPY_PY_DIR}/modmicropython.c + ${MICROPY_PY_DIR}/modstruct.c + ${MICROPY_PY_DIR}/modsys.c + ${MICROPY_PY_DIR}/modthread.c + ${MICROPY_PY_DIR}/moduerrno.c + ${MICROPY_PY_DIR}/mpprint.c + ${MICROPY_PY_DIR}/mpstate.c + ${MICROPY_PY_DIR}/mpz.c + ${MICROPY_PY_DIR}/nativeglue.c + ${MICROPY_PY_DIR}/nlr.c + ${MICROPY_PY_DIR}/nlrpowerpc.c + ${MICROPY_PY_DIR}/nlrsetjmp.c + ${MICROPY_PY_DIR}/nlrthumb.c + ${MICROPY_PY_DIR}/nlrx64.c + ${MICROPY_PY_DIR}/nlrx86.c + ${MICROPY_PY_DIR}/nlrxtensa.c + ${MICROPY_PY_DIR}/obj.c + ${MICROPY_PY_DIR}/objarray.c + ${MICROPY_PY_DIR}/objattrtuple.c + ${MICROPY_PY_DIR}/objbool.c + ${MICROPY_PY_DIR}/objboundmeth.c + ${MICROPY_PY_DIR}/objcell.c + ${MICROPY_PY_DIR}/objclosure.c + ${MICROPY_PY_DIR}/objcomplex.c + ${MICROPY_PY_DIR}/objdeque.c + ${MICROPY_PY_DIR}/objdict.c + ${MICROPY_PY_DIR}/objenumerate.c + ${MICROPY_PY_DIR}/objexcept.c + ${MICROPY_PY_DIR}/objfilter.c + ${MICROPY_PY_DIR}/objfloat.c + ${MICROPY_PY_DIR}/objfun.c + ${MICROPY_PY_DIR}/objgenerator.c + ${MICROPY_PY_DIR}/objgetitemiter.c + ${MICROPY_PY_DIR}/objint.c + ${MICROPY_PY_DIR}/objint_longlong.c + ${MICROPY_PY_DIR}/objint_mpz.c + ${MICROPY_PY_DIR}/objlist.c + ${MICROPY_PY_DIR}/objmap.c + ${MICROPY_PY_DIR}/objmodule.c + ${MICROPY_PY_DIR}/objnamedtuple.c + ${MICROPY_PY_DIR}/objnone.c + ${MICROPY_PY_DIR}/objobject.c + ${MICROPY_PY_DIR}/objpolyiter.c + ${MICROPY_PY_DIR}/objproperty.c + ${MICROPY_PY_DIR}/objrange.c + ${MICROPY_PY_DIR}/objreversed.c + ${MICROPY_PY_DIR}/objset.c + ${MICROPY_PY_DIR}/objsingleton.c + ${MICROPY_PY_DIR}/objslice.c + ${MICROPY_PY_DIR}/objstr.c + ${MICROPY_PY_DIR}/objstringio.c + ${MICROPY_PY_DIR}/objstrunicode.c + ${MICROPY_PY_DIR}/objtuple.c + ${MICROPY_PY_DIR}/objtype.c + ${MICROPY_PY_DIR}/objzip.c + ${MICROPY_PY_DIR}/opmethods.c + ${MICROPY_PY_DIR}/pairheap.c + ${MICROPY_PY_DIR}/parse.c + ${MICROPY_PY_DIR}/parsenum.c + ${MICROPY_PY_DIR}/parsenumbase.c + ${MICROPY_PY_DIR}/persistentcode.c + ${MICROPY_PY_DIR}/profile.c + ${MICROPY_PY_DIR}/pystack.c + ${MICROPY_PY_DIR}/qstr.c + ${MICROPY_PY_DIR}/reader.c + ${MICROPY_PY_DIR}/repl.c + ${MICROPY_PY_DIR}/ringbuf.c + ${MICROPY_PY_DIR}/runtime.c + ${MICROPY_PY_DIR}/runtime_utils.c + ${MICROPY_PY_DIR}/scheduler.c + ${MICROPY_PY_DIR}/scope.c + ${MICROPY_PY_DIR}/sequence.c + ${MICROPY_PY_DIR}/showbc.c + ${MICROPY_PY_DIR}/smallint.c + ${MICROPY_PY_DIR}/stackctrl.c + ${MICROPY_PY_DIR}/stream.c + ${MICROPY_PY_DIR}/unicode.c + ${MICROPY_PY_DIR}/vm.c + ${MICROPY_PY_DIR}/vstr.c + ${MICROPY_PY_DIR}/warning.c +) diff --git a/py/py.mk b/py/py.mk index d864a7ed3..bac38f074 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 diff --git a/py/pystack.c b/py/pystack.c index f7323fd74..cc4b5e2e4 100644 --- a/py/pystack.c +++ b/py/pystack.c @@ -31,9 +31,9 @@ #if MICROPY_ENABLE_PYSTACK void mp_pystack_init(void *start, void *end) { - MP_STATE_THREAD(pystack_start) = start; - MP_STATE_THREAD(pystack_end) = end; - MP_STATE_THREAD(pystack_cur) = start; + MP_STATE_THREAD(pystack_start) = (uint8_t *)start; + MP_STATE_THREAD(pystack_end) = (uint8_t *)end; + MP_STATE_THREAD(pystack_cur) = (uint8_t *)start; } void *mp_pystack_alloc(size_t n_bytes) { @@ -43,8 +43,15 @@ void *mp_pystack_alloc(size_t n_bytes) { #endif if (MP_STATE_THREAD(pystack_cur) + n_bytes > MP_STATE_THREAD(pystack_end)) { // out of memory in the pystack +#if NO_NLR + fprintf(stderr,"mp_pystack_alloc '%p' + '%ld' > '%p'\n", MP_STATE_THREAD(pystack_cur) , (size_t)n_bytes , MP_STATE_THREAD(pystack_end) ); + mp_raise_o(mp_obj_new_exception_arg1(&mp_type_RuntimeError, + MP_OBJ_NEW_QSTR(MP_QSTR_pystack_space_exhausted))); + return NULL; +#else nlr_raise(mp_obj_new_exception_arg1(&mp_type_RuntimeError, MP_OBJ_NEW_QSTR(MP_QSTR_pystack_space_exhausted))); +#endif } void *ptr = MP_STATE_THREAD(pystack_cur); MP_STATE_THREAD(pystack_cur) += n_bytes; diff --git a/py/qstr.c b/py/qstr.c index c14ec5ae0..14b11f628 100644 --- a/py/qstr.c +++ b/py/qstr.c @@ -99,7 +99,7 @@ mp_uint_t qstr_compute_hash(const byte *data, size_t len) { } return hash; } - +#pragma message "error: initialization of flexible array member is not allowed" const qstr_pool_t mp_qstr_const_pool = { NULL, // no previous pool 0, // no previous pool @@ -155,6 +155,9 @@ STATIC qstr qstr_add(const byte *q_ptr) { if (pool == NULL) { QSTR_EXIT(); m_malloc_fail(new_alloc); +#if NO_NLR + return 0; +#endif } pool->prev = MP_STATE_VM(last_pool); pool->total_prev_len = MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len; @@ -201,7 +204,12 @@ qstr qstr_from_strn(const char *str, size_t len) { // check that len is not too big if (len >= (1 << (8 * MICROPY_QSTR_BYTES_IN_LEN))) { QSTR_EXIT(); +#if NO_NLR + mp_raise_msg_o(&mp_type_RuntimeError, MP_ERROR_TEXT("name too long")); + return 0; +#else mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("name too long")); +#endif } // compute number of bytes needed to intern this string @@ -233,6 +241,9 @@ qstr qstr_from_strn(const char *str, size_t len) { if (MP_STATE_VM(qstr_last_chunk) == NULL) { QSTR_EXIT(); m_malloc_fail(n_bytes); +#if NO_NLR + return 0; +#endif } al = n_bytes; } diff --git a/py/qstr.h b/py/qstr.h index df87217ff..0b6fb12b0 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); diff --git a/py/reader.c b/py/reader.c index d68406b1c..c13bf1a24 100644 --- a/py/reader.c +++ b/py/reader.c @@ -113,6 +113,11 @@ STATIC void mp_reader_posix_close(void *data) { void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { mp_reader_posix_t *rp = m_new_obj(mp_reader_posix_t); +#if NO_NLR + if (rp == NULL) { + return; + } +#endif rp->close_fd = close_fd; rp->fd = fd; MP_THREAD_GIL_EXIT(); @@ -122,7 +127,12 @@ void mp_reader_new_file_from_fd(mp_reader_t *reader, int fd, bool close_fd) { close(fd); } MP_THREAD_GIL_ENTER(); +#if NO_NLR + mp_raise_OSError_o(errno); + return; +#else mp_raise_OSError(errno); +#endif } MP_THREAD_GIL_ENTER(); rp->len = n; @@ -139,7 +149,13 @@ void mp_reader_new_file(mp_reader_t *reader, const char *filename) { int fd = open(filename, O_RDONLY, 0644); MP_THREAD_GIL_ENTER(); if (fd < 0) { +#if NO_NLR + mp_raise_OSError_o(errno); + return; +#else mp_raise_OSError(errno); +#endif + } mp_reader_new_file_from_fd(reader, fd, true); } diff --git a/py/ringbuf.c b/py/ringbuf.c index 83887b300..8f049a237 100644 --- a/py/ringbuf.c +++ b/py/ringbuf.c @@ -23,7 +23,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include "ringbuf.h" +#include "py/ringbuf.h" int ringbuf_get16(ringbuf_t *r) { int v = ringbuf_peek16(r); diff --git a/py/runtime.c b/py/runtime.c index c12271f4e..f0d2a8930 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -24,6 +24,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#if NO_NLR +#error "Wrong file: use _no_nlr.c" +#endif #include #include @@ -387,7 +390,9 @@ mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { if (rhs_val < 0) { // negative shift not allowed mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); - } else if (rhs_val >= (mp_int_t)BITS_PER_WORD || lhs_val > (MP_SMALL_INT_MAX >> rhs_val) || lhs_val < (MP_SMALL_INT_MIN >> rhs_val)) { + } else if (rhs_val >= (mp_int_t)(sizeof(lhs_val) * MP_BITS_PER_BYTE) + || lhs_val > (MP_SMALL_INT_MAX >> rhs_val) + || lhs_val < (MP_SMALL_INT_MIN >> rhs_val)) { // left-shift will overflow, so use higher precision integer lhs = mp_obj_new_int_from_ll(lhs_val); goto generic_binary_op; @@ -404,10 +409,10 @@ mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } else { // standard precision is enough for right-shift - if (rhs_val >= (mp_int_t)BITS_PER_WORD) { + if (rhs_val >= (mp_int_t)(sizeof(lhs_val) * MP_BITS_PER_BYTE)) { // Shifting to big amounts is underfined behavior // in C and is CPU-dependent; propagate sign bit. - rhs_val = BITS_PER_WORD - 1; + rhs_val = sizeof(lhs_val) * MP_BITS_PER_BYTE - 1; } lhs_val >>= rhs_val; } @@ -1086,6 +1091,13 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { dest[0] = MP_OBJ_FROM_PTR(type); return; } + if (attr == MP_QSTR___base__) { // PMPP https://github.com/micropython/micropython/pull/4368 + const mp_obj_type_t *t = MP_OBJ_TO_PTR(obj); + if (mp_obj_is_instance_type(t)) { + dest[0] = MP_OBJ_FROM_PTR(t->parent); + return; // success + } + } #endif if (attr == MP_QSTR___next__ && type->iternext != NULL) { @@ -1106,6 +1118,13 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) { mp_convert_member_lookup(obj, type, elem->value, dest); } } + #if MICROPY_PY_FUNCTION_ATTRS //PMPP + else if (attr == MP_QSTR___name__) { + if ( type->name == MP_QSTR_function) { + dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(obj)); + } + } + #endif } void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) { diff --git a/py/runtime.h b/py/runtime.h index 0bf988b90..84043ce90 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -29,6 +29,193 @@ #include "py/mpstate.h" #include "py/pystack.h" +#if NO_NLR + +typedef enum { + MP_VM_RETURN_NORMAL, + MP_VM_RETURN_YIELD, + MP_VM_RETURN_EXCEPTION, +} mp_vm_return_kind_t; + +typedef enum { + MP_ARG_BOOL = 0x001, + MP_ARG_INT = 0x002, + MP_ARG_OBJ = 0x003, + MP_ARG_KIND_MASK = 0x0ff, + MP_ARG_REQUIRED = 0x100, + MP_ARG_KW_ONLY = 0x200, +} mp_arg_flag_t; + +typedef union _mp_arg_val_t { + bool u_bool; + mp_int_t u_int; + mp_obj_t u_obj; + mp_rom_obj_t u_rom_obj; +} mp_arg_val_t; + +typedef struct _mp_arg_t { + uint16_t qst; + uint16_t flags; + mp_arg_val_t defval; +} mp_arg_t; + +// Tables mapping operator enums to qstrs, defined in objtype.c +extern const byte mp_unary_op_method_name[]; +extern const byte mp_binary_op_method_name[]; + +void mp_init(void); +void mp_deinit(void); + +void mp_keyboard_interrupt(void); +void mp_handle_pending(bool raise_exc); +void mp_handle_pending_tail(mp_uint_t atomic_state); + +#if MICROPY_ENABLE_SCHEDULER +void mp_sched_lock(void); +void mp_sched_unlock(void); +static inline unsigned int mp_sched_num_pending(void) { + return MP_STATE_VM(sched_len); +} +bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg); +#endif + +// extra printing method specifically for mp_obj_t's which are integral type +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec); + +int mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig); +static inline int mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_min, size_t n_args_max, bool takes_kw) { + return mp_arg_check_num_sig(n_args, n_kw, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, takes_kw)); +} +int mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +int mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); +void mp_arg_error_terse_mismatch(void); +void mp_arg_error_unimpl_kw(void); + +static inline mp_obj_dict_t *mp_locals_get(void) { + return MP_STATE_THREAD(dict_locals); +} +static inline void mp_locals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_locals) = d; +} +static inline mp_obj_dict_t *mp_globals_get(void) { + return MP_STATE_THREAD(dict_globals); +} +static inline void mp_globals_set(mp_obj_dict_t *d) { + MP_STATE_THREAD(dict_globals) = d; +} + +mp_obj_t mp_load_name(qstr qst); +mp_obj_t mp_load_global(qstr qst); +mp_obj_t mp_load_build_class(void); +void mp_store_name(qstr qst, mp_obj_t obj); +void mp_store_global(qstr qst, mp_obj_t obj); +mp_obj_t mp_delete_name(qstr qst); +mp_obj_t mp_delete_global(qstr qst); + +mp_obj_t mp_unary_op(mp_unary_op_t op, mp_obj_t arg); +mp_obj_t mp_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs); + +mp_obj_t mp_call_function_0(mp_obj_t fun); +mp_obj_t mp_call_function_1(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); +mp_obj_t mp_call_function_n_kw(mp_obj_t fun, size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw(size_t n_args, size_t n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args); +mp_obj_t mp_call_method_self_n_kw(mp_obj_t meth, mp_obj_t self, size_t n_args, size_t n_kw, const mp_obj_t *args); +// Call function and catch/dump exception - for Python callbacks from C code +// (return MP_OBJ_NULL in case of exception). +mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg); +mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2); + +typedef struct _mp_call_args_t { + mp_obj_t fun; + size_t n_args, n_kw, n_alloc; + mp_obj_t *args; +} mp_call_args_t; + +#if MICROPY_STACKLESS +// Takes arguments which are the most general mix of Python arg types, and +// prepares argument array suitable for passing to ->call() method of a +// function object (and mp_call_function_n_kw()). +// (Only needed in stackless mode.) +int mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_obj_t *args, mp_call_args_t *out_args); +#endif + +mp_obj_t mp_unpack_sequence(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_unpack_ex(mp_obj_t seq, size_t num, mp_obj_t *items); +mp_obj_t mp_store_map(mp_obj_t map, mp_obj_t key, mp_obj_t value); +mp_obj_t mp_load_attr(mp_obj_t base, qstr attr); +void mp_convert_member_lookup(mp_obj_t obj, const mp_obj_type_t *type, mp_obj_t member, mp_obj_t *dest); +mp_obj_t mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_maybe(mp_obj_t base, qstr attr, mp_obj_t *dest); +mp_obj_t mp_load_method_protected(mp_obj_t obj, qstr attr, mp_obj_t *dest, bool catch_all_exc); +mp_obj_t mp_load_super_method(qstr attr, mp_obj_t *dest); +mp_obj_t mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t val); + +mp_obj_t mp_getiter(mp_obj_t o, mp_obj_iter_buf_t *iter_buf); +mp_obj_t mp_iternext_allow_raise(mp_obj_t o); // may return MP_OBJ_STOP_ITERATION instead of raising StopIteration() +mp_obj_t mp_iternext(mp_obj_t o); // will always return MP_OBJ_STOP_ITERATION instead of raising StopIteration(...) +mp_obj_t mp_iternext2(mp_obj_t o); +bool mp_iternext_had_exc(void); +mp_vm_return_kind_t mp_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val); + +mp_obj_t mp_make_raise_obj(mp_obj_t o); + +mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level); +mp_obj_t mp_import_from(mp_obj_t module, qstr name); +void mp_import_all(mp_obj_t module); + +void mp_raise_recursion_depth(void); +mp_obj_t mp_raise_o(mp_obj_t exc); +mp_obj_t mp_raise_msg_o(const mp_obj_type_t *exc_type, const char *msg); +mp_obj_t mp_raise_ValueError_o(const char *msg); +mp_obj_t mp_raise_TypeError_o(const char *msg); +mp_obj_t mp_raise_NotImplementedError_o(const char *msg); +mp_obj_t mp_raise_OSError_o(int errno_); + + +#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG +#undef mp_check_self +#define mp_check_self(pred) +#else +// A port may define to raise TypeError for example +#ifndef mp_check_self +#define mp_check_self(pred) assert(pred) +#endif +#endif + +// helper functions for native/viper code +int mp_native_type_from_qstr(qstr qst); +mp_uint_t mp_native_from_obj(mp_obj_t obj, mp_uint_t type); +mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type); + +#define mp_sys_path (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_path_obj))) +#define mp_sys_argv (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_argv_obj))) + +#if MICROPY_WARNINGS +#ifndef mp_warning +void mp_warning(const char *category, const char *msg, ...); +#endif +#else +#define mp_warning(...) +#endif + + +#define mp_raise_or_return(exc) { return mp_raise_o(exc); } +#define mp_raise_or_return_value(exc, retval) { mp_raise_o(exc); return retval; } +#define mp_raise_OSError(errno) { return mp_raise_OSError_o(errno); } +#define mp_raise_OSError_or_return(errno, retval) { mp_raise_OSError_o(errno); return retval; } +#define mp_raise_type(extype) { return mp_raise_o(mp_obj_new_exception(extype)); } +#define mp_raise_type_or_return(extype, retval) { mp_raise_o(mp_obj_new_exception(extype)); return retval; } +#define mp_raise_ValueError(v) { return mp_raise_ValueError_o(v); } +//#define mp_raise_msg(extype, mpt) { return mp_raise_o( mp_obj_new_exception_msg(extype, mpt)); } +#define mp_raise_msg(extype, mpt) { return mp_raise_msg_o(extype, mpt); } +#define mp_raise_TypeError(mpt) { return mp_raise_TypeError_o(mpt); } +#define mp_raise_TypeError_or_return(mpt, retval) { mp_raise_TypeError_o(mpt); return retval; } +#define mp_raise_NotImplementedError(mpt) { return mp_raise_NotImplementedError_o(mpt); } + +#else // NO_NLR + typedef enum { MP_VM_RETURN_NORMAL, MP_VM_RETURN_YIELD, @@ -159,7 +346,7 @@ mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level); mp_obj_t mp_import_from(mp_obj_t module, qstr name); void mp_import_all(mp_obj_t module); -#define mp_raise_type(exc_type) mp_raise_msg(exc_type, NULL) +//#define mp_raise_type(exc_type) mp_raise_msg(exc_type, NULL) NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...); NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg); @@ -194,4 +381,19 @@ void mp_warning(const char *category, const char *msg, ...); #define mp_warning(...) #endif +// NO_NLR compat +#define mp_raise_or_return(exc) nlr_raise(exc) +#define mp_raise_or_return_value(exc, retval) nlr_raise(exc) +// #define mp_raise_OSError(exc) +#define mp_raise_OSError_or_return(errno, retval) mp_raise_OSError(errno) +// #define mp_raise_type(extype) +#define mp_raise_type_or_return(extype, retval) mp_raise_type(extype) +// #define mp_raise_ValueError(v) +// #define mp_raise_msg(extype, mpt) +// #define mp_raise_TypeError(mpt) +#define mp_raise_TypeError_or_return(mpt, retval) +//#define mp_raise_NotImplementedError(mpt) + +#endif + #endif // MICROPY_INCLUDED_PY_RUNTIME_H diff --git a/py/runtime_utils.c b/py/runtime_utils.c index b92c6bd76..e877c6d5d 100644 --- a/py/runtime_utils.c +++ b/py/runtime_utils.c @@ -27,6 +27,25 @@ #include "py/runtime.h" +#if NO_NLR +mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg) { + mp_obj_t ret = mp_call_function_1(fun, arg); + if (ret == MP_OBJ_NULL) { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception))); + MP_STATE_THREAD(active_exception) = NULL; + } + return ret; +} + +mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2) { + mp_obj_t ret = mp_call_function_2(fun, arg1, arg2); + if (ret == MP_OBJ_NULL) { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception))); + MP_STATE_THREAD(active_exception) = NULL; + } + return ret; +} +#else mp_obj_t mp_call_function_1_protected(mp_obj_t fun, mp_obj_t arg) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { @@ -50,3 +69,4 @@ mp_obj_t mp_call_function_2_protected(mp_obj_t fun, mp_obj_t arg1, mp_obj_t arg2 return MP_OBJ_NULL; } } +#endif // NO_NLR diff --git a/py/scheduler.c b/py/scheduler.c index 6b138a631..421c077b9 100644 --- a/py/scheduler.c +++ b/py/scheduler.c @@ -144,11 +144,15 @@ bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj // A variant of this is inlined in the VM at the pending exception check void mp_handle_pending(bool raise_exc) { if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) { +#if NO_NLR +#pragma message "mp_handle_pending what, where and how ?" +#else mp_obj_t obj = MP_STATE_VM(mp_pending_exception); MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; if (raise_exc) { nlr_raise(obj); } +#endif } } diff --git a/py/scope.c b/py/scope.c index 073c2a38c..859a233d8 100644 --- a/py/scope.c +++ b/py/scope.c @@ -30,7 +30,7 @@ #if MICROPY_ENABLE_COMPILER -// These low numbered qstrs should fit in 8 bits. See assertions below. +// These low numbered qstrs should fit in 8 bits STATIC const uint8_t scope_simple_name_table[] = { [SCOPE_MODULE] = MP_QSTR__lt_module_gt_, [SCOPE_LAMBDA] = MP_QSTR__lt_lambda_gt_, @@ -50,6 +50,11 @@ scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_u MP_STATIC_ASSERT(MP_QSTR__lt_genexpr_gt_ <= UINT8_MAX); scope_t *scope = m_new0(scope_t, 1); +#if NO_NLR + if (scope == NULL) { + return NULL; + } +#endif scope->kind = kind; scope->pn = pn; scope->source_file = source_file; @@ -64,6 +69,11 @@ scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_u scope->id_info_alloc = MICROPY_ALLOC_SCOPE_ID_INIT; scope->id_info = m_new(id_info_t, scope->id_info_alloc); +#if NO_NLR + if (scope->raw_code == NULL || scope->id_info == NULL) { + return NULL; + } +#endif return scope; } @@ -72,7 +82,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; @@ -81,6 +91,11 @@ id_info_t *scope_find_or_add_id(scope_t *scope, qstr qst, scope_kind_t kind) { // make sure we have enough memory if (scope->id_info_len >= scope->id_info_alloc) { scope->id_info = m_renew(id_info_t, scope->id_info, scope->id_info_alloc, scope->id_info_alloc + MICROPY_ALLOC_SCOPE_ID_INC); +#if NO_NLR + if (scope->id_info == NULL) { + return NULL; + } +#endif scope->id_info_alloc += MICROPY_ALLOC_SCOPE_ID_INC; } @@ -97,6 +112,11 @@ id_info_t *scope_find_or_add_id(scope_t *scope, qstr qst, scope_kind_t kind) { } id_info_t *scope_find(scope_t *scope, qstr qst) { +#if NO_NLR + if (scope->id_info == NULL) { + return NULL; + } +#endif for (mp_uint_t i = 0; i < scope->id_info_len; i++) { if (scope->id_info[i].qst == qst) { return &scope->id_info[i]; diff --git a/py/scope.h b/py/scope.h index b52d98ea1..edf164c4a 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); diff --git a/py/sequence.c b/py/sequence.c index fa660a338..1f6c1517b 100644 --- a/py/sequence.c +++ b/py/sequence.c @@ -44,10 +44,18 @@ void mp_seq_multiply(const void *items, size_t item_sz, size_t len, size_t times } #if MICROPY_PY_BUILTINS_SLICE - +#if NO_NLR +int mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes) { +#else bool mp_seq_get_fast_slice_indexes(mp_uint_t len, mp_obj_t slice, mp_bound_slice_t *indexes) { - mp_obj_slice_indices(slice, len, indexes); +#endif + mp_obj_slice_indices(slice, len, indexes); +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return -1; + } +#endif // If the index is negative then stop points to the last item, not after it if (indexes->step < 0) { indexes->stop++; diff --git a/py/smallint.h b/py/smallint.h index 6a3c75236..58d843e8a 100644 --- a/py/smallint.h +++ b/py/smallint.h @@ -36,17 +36,17 @@ // In SMALL_INT, next-to-highest bits is used as sign, so both must match for value in range #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C -#define MP_SMALL_INT_MIN ((mp_int_t)(((mp_int_t)WORD_MSBIT_HIGH) >> 1)) -#define MP_SMALL_INT_FITS(n) ((((n) ^ ((n) << 1)) & WORD_MSBIT_HIGH) == 0) +#define MP_SMALL_INT_MIN ((mp_int_t)(((mp_int_t)MP_OBJ_WORD_MSBIT_HIGH) >> 1)) +#define MP_SMALL_INT_FITS(n) ((((n) ^ ((n) << 1)) & MP_OBJ_WORD_MSBIT_HIGH) == 0) // Mask to truncate mp_int_t to positive value -#define MP_SMALL_INT_POSITIVE_MASK ~(WORD_MSBIT_HIGH | (WORD_MSBIT_HIGH >> 1)) +#define MP_SMALL_INT_POSITIVE_MASK ~(MP_OBJ_WORD_MSBIT_HIGH | (MP_OBJ_WORD_MSBIT_HIGH >> 1)) #elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B -#define MP_SMALL_INT_MIN ((mp_int_t)(((mp_int_t)WORD_MSBIT_HIGH) >> 2)) +#define MP_SMALL_INT_MIN ((mp_int_t)(((mp_int_t)MP_OBJ_WORD_MSBIT_HIGH) >> 2)) #define MP_SMALL_INT_FITS(n) ((((n) & MP_SMALL_INT_MIN) == 0) || (((n) & MP_SMALL_INT_MIN) == MP_SMALL_INT_MIN)) // Mask to truncate mp_int_t to positive value -#define MP_SMALL_INT_POSITIVE_MASK ~(WORD_MSBIT_HIGH | (WORD_MSBIT_HIGH >> 1) | (WORD_MSBIT_HIGH >> 2)) +#define MP_SMALL_INT_POSITIVE_MASK ~(MP_OBJ_WORD_MSBIT_HIGH | (MP_OBJ_WORD_MSBIT_HIGH >> 1) | (MP_OBJ_WORD_MSBIT_HIGH >> 2)) #elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D diff --git a/py/stackctrl.c b/py/stackctrl.c index c2f3adb5e..b2b249fbd 100644 --- a/py/stackctrl.c +++ b/py/stackctrl.c @@ -48,10 +48,20 @@ void mp_stack_set_limit(mp_uint_t limit) { MP_STATE_THREAD(stack_limit) = limit; } +#if NO_NLR +int mp_stack_check(void) { + if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { + mp_raise_recursion_depth(); + + return 1; + } + return 0; +} +#else void mp_stack_check(void) { if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { mp_raise_recursion_depth(); } } - +#endif #endif // MICROPY_STACK_CHECK diff --git a/py/stackctrl.h b/py/stackctrl.h index ff8da0ab1..6234a2361 100644 --- a/py/stackctrl.h +++ b/py/stackctrl.h @@ -35,14 +35,23 @@ mp_uint_t mp_stack_usage(void); #if MICROPY_STACK_CHECK void mp_stack_set_limit(mp_uint_t limit); +#if NO_NLR +int mp_stack_check(void); +#else void mp_stack_check(void); +#endif #define MP_STACK_CHECK() mp_stack_check() #else #define mp_stack_set_limit(limit) +#if NO_NLR +static inline int MP_STACK_CHECK(void) { + return 0; +} +#else #define MP_STACK_CHECK() - +#endif // NO_NLR #endif #endif // MICROPY_INCLUDED_PY_STACKCTRL_H diff --git a/py/stream.c b/py/stream.c index dce64a44d..563178edc 100644 --- a/py/stream.c +++ b/py/stream.c @@ -92,7 +92,12 @@ const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) { || ((flags & MP_STREAM_OP_WRITE) && stream_p->write == NULL) || ((flags & MP_STREAM_OP_IOCTL) && stream_p->ioctl == NULL)) { // CPython: io.UnsupportedOperation, OSError subclass +#if NO_NLR + mp_raise_msg_o(&mp_type_OSError, MP_ERROR_TEXT("stream operation not supported")); + return NULL; +#else mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("stream operation not supported")); +#endif } return stream_p; } @@ -312,6 +317,11 @@ STATIC mp_obj_t stream_readall(mp_obj_t self_in) { int error; mp_uint_t out_sz = stream_p->read(self_in, p, current_read, &error); if (out_sz == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif if (mp_is_nonblocking_error(error)) { // With non-blocking streams, we read as much as we can. // If we read nothing, return None, just like read(). @@ -361,6 +371,11 @@ STATIC mp_obj_t stream_unbuffered_readline(size_t n_args, const mp_obj_t *args) int error; mp_uint_t out_sz = stream_p->read(args[0], p, 1, &error); if (out_sz == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif if (mp_is_nonblocking_error(error)) { if (vstr.len == 1) { // We just incremented it, but otherwise we read nothing @@ -421,6 +436,11 @@ mp_obj_t mp_stream_close(mp_obj_t stream) { int error; mp_uint_t res = stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } return mp_const_none; @@ -445,6 +465,12 @@ STATIC mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) { int error; mp_uint_t res = stream_p->ioctl(args[0], MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR +#pragma message "TODO: Could be uint64" + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } @@ -466,6 +492,11 @@ STATIC mp_obj_t stream_flush(mp_obj_t self) { int error; mp_uint_t res = stream_p->ioctl(self, MP_STREAM_FLUSH, 0, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } return mp_const_none; @@ -487,6 +518,11 @@ STATIC mp_obj_t stream_ioctl(size_t n_args, const mp_obj_t *args) { int error; mp_uint_t res = stream_p->ioctl(args[0], mp_obj_get_int(args[1]), val, &error); if (res == MP_STREAM_ERROR) { +#if NO_NLR + if (MP_STATE_THREAD(active_exception) != NULL) { + return MP_OBJ_NULL; + } +#endif mp_raise_OSError(error); } diff --git a/py/vm.c b/py/vm.c index 393a2bbbd..7afdbc88c 100644 --- a/py/vm.c +++ b/py/vm.c @@ -37,6 +37,9 @@ #include "py/profile.h" // *FORMAT-OFF* +#if WAPY +#include "../wapy/upython.h" +#endif #if 0 #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); @@ -229,7 +232,20 @@ mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp // sees that it's possible for us to jump from the dispatch loop to the exception // handler. Without this, the code may have a different stack layout in the dispatch // loop and the exception handler, leading to very obscure bugs. +#if NO_NLR + #define RAISE(o) do { MP_STATE_THREAD(active_exception) = MP_OBJ_TO_PTR(o); goto exception_handler; } while (0) + //#define RAISE_IT() do { goto exception_handler; } while (0) + #define RAISE_IF(arg) if (arg) { goto exception_handler; } + #define RAISE_IF_NULL(arg) RAISE_IF(arg == MP_OBJ_NULL) + #define THE_EXC the_exc + #define TEST_TOP() (*sp) +#else #define RAISE(o) do { nlr_pop(); nlr.ret_val = MP_OBJ_TO_PTR(o); goto exception_handler; } while (0) + #define RAISE_IF(condition) { condition; } + #define THE_EXC nlr.ret_val + #define RAISE_IF_NULL(arg) {arg;} + #define TEST_TOP() +#endif #if MICROPY_STACKLESS run_code_state: ; @@ -259,11 +275,48 @@ FRAME_SETUP(); volatile int gil_divisor = MICROPY_PY_THREAD_GIL_VM_DIVISOR; #endif +#if WAPY +if (VMFLAGS_IF>0) { + static size_t source_line=0; + const byte *ip = code_state->fun_bc->bytecode; + MP_BC_PRELUDE_SIG_DECODE(ip); + MP_BC_PRELUDE_SIZE_DECODE(ip); + const byte *bytecode_start = ip + n_info + n_cell; + #if !MICROPY_PERSISTENT_CODE + // so bytecode is aligned + bytecode_start = MP_ALIGN(bytecode_start, sizeof(mp_uint_t)); + #endif + size_t bc = code_state->ip - bytecode_start; + #if MICROPY_PERSISTENT_CODE + qstr block_name = ip[0] | (ip[1] << 8); + qstr source_file = ip[2] | (ip[3] << 8); + ip += 4; + #else + qstr block_name = mp_decode_uint_value(ip); + ip = mp_decode_uint_skip(ip); + qstr source_file = mp_decode_uint_value(ip); + ip = mp_decode_uint_skip(ip); + #endif + size_t the_line = mp_bytecode_get_source_line(ip, bc); + if (the_line != source_line) { + source_line = the_line; + if (strcmp(qstr_str(source_file),"pythons/aio/plink.py")) + cdbg("247:vm.c NOINT src/%s#%lu but VMFLAGS_IF set", qstr_str(source_file), source_line); + } +} +#else + #pragma message "no wapy vm check" +#endif + // outer exception handling loop for (;;) { +#if NO_NLR + { +#else nlr_buf_t nlr; outer_dispatch_loop: if (nlr_push(&nlr) == 0) { +#endif // local variables that are not visible to the exception handler const byte *ip = code_state->ip; mp_obj_t *sp = code_state->sp; @@ -338,12 +391,12 @@ FRAME_SETUP(); ENTRY(MP_BC_LOAD_FAST_N): { DECODE_UINT; obj_shared = fastn[-unum]; - load_check: +load_check: if (obj_shared == MP_OBJ_NULL) { - local_name_error: { +local_name_error: + { MARK_EXC_IP_SELECTIVE(); - mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NameError, MP_ERROR_TEXT("local variable referenced before assignment")); - RAISE(obj); + RAISE(mp_obj_new_exception_msg(&mp_type_NameError, "local variable referenced before assignment")); } } PUSH(obj_shared); @@ -361,6 +414,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_name(qst)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #else @@ -373,6 +427,9 @@ FRAME_SETUP(); obj = elem->value; } else { obj = mp_load_name(qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif } PUSH(obj); ip++; @@ -385,6 +442,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; PUSH(mp_load_global(qst)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #else @@ -397,6 +455,9 @@ FRAME_SETUP(); obj = elem->value; } else { obj = mp_load_global(qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif } PUSH(obj); ip++; @@ -410,6 +471,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; SET_TOP(mp_load_attr(TOP(), qst)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #else @@ -428,6 +490,9 @@ FRAME_SETUP(); obj = elem->value; } else { obj = mp_load_attr(top, qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif } SET_TOP(obj); ip++; @@ -438,7 +503,7 @@ FRAME_SETUP(); ENTRY(MP_BC_LOAD_METHOD): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_load_method(*sp, qst, sp); + RAISE_IF_NULL(mp_load_method(*sp, qst, sp)); sp += 1; DISPATCH(); } @@ -447,7 +512,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; sp -= 1; - mp_load_super_method(qst, sp - 1); + RAISE_IF_NULL(mp_load_super_method(qst, sp - 1)); DISPATCH(); } @@ -460,6 +525,7 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); mp_obj_t index = POP(); SET_TOP(mp_obj_subscr(TOP(), index, MP_OBJ_SENTINEL)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -494,7 +560,7 @@ FRAME_SETUP(); FRAME_UPDATE(); MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_store_attr(sp[0], qst, sp[-1]); + RAISE_IF_NULL(mp_store_attr(sp[0], qst, sp[-1])); sp -= 2; DISPATCH(); } @@ -517,7 +583,7 @@ FRAME_SETUP(); if (elem != NULL) { elem->value = sp[-1]; } else { - mp_store_attr(sp[0], qst, sp[-1]); + RAISE_IF_NULL(mp_store_attr(sp[0], qst, sp[-1])); } sp -= 2; ip++; @@ -527,7 +593,7 @@ FRAME_SETUP(); ENTRY(MP_BC_STORE_SUBSCR): MARK_EXC_IP_SELECTIVE(); - mp_obj_subscr(sp[-1], sp[0], sp[-2]); + RAISE_IF_NULL(mp_obj_subscr(sp[-1], sp[0], sp[-2])); sp -= 3; DISPATCH(); @@ -554,14 +620,14 @@ FRAME_SETUP(); ENTRY(MP_BC_DELETE_NAME): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_delete_name(qst); + RAISE_IF_NULL(mp_delete_name(qst)); DISPATCH(); } ENTRY(MP_BC_DELETE_GLOBAL): { MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; - mp_delete_global(qst); + RAISE_IF_NULL(mp_delete_global(qst)); DISPATCH(); } @@ -642,9 +708,12 @@ FRAME_SETUP(); MARK_EXC_IP_SELECTIVE(); // stack: (..., ctx_mgr) mp_obj_t obj = TOP(); - mp_load_method(obj, MP_QSTR___exit__, sp); - mp_load_method(obj, MP_QSTR___enter__, sp + 2); + RAISE_IF_NULL(mp_load_method(obj, MP_QSTR___exit__, sp)); + RAISE_IF_NULL(mp_load_method(obj, MP_QSTR___enter__, sp + 2)); mp_obj_t ret = mp_call_method_n_kw(0, 0, sp + 2); +#if NO_NLR + RAISE_IF_NULL(ret); +#endif sp += 1; PUSH_EXC_BLOCK(1); PUSH(ret); @@ -665,7 +734,7 @@ FRAME_SETUP(); sp[1] = mp_const_none; sp[2] = mp_const_none; sp -= 2; - mp_call_method_n_kw(3, 0, sp); + RAISE_IF_NULL(mp_call_method_n_kw(3, 0, sp)); SET_TOP(mp_const_none); } else if (mp_obj_is_small_int(TOP())) { // Getting here there are two distinct cases: @@ -790,6 +859,7 @@ unwind_jump:; ENTRY(MP_BC_GET_ITER): MARK_EXC_IP_SELECTIVE(); SET_TOP(mp_getiter(TOP(), NULL)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); // An iterator for a for-loop takes MP_OBJ_ITER_BUF_NSLOTS slots on @@ -802,6 +872,9 @@ unwind_jump:; mp_obj_iter_buf_t *iter_buf = (mp_obj_iter_buf_t*)sp; sp += MP_OBJ_ITER_BUF_NSLOTS - 1; obj = mp_getiter(obj, iter_buf); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif if (obj != MP_OBJ_FROM_PTR(iter_buf)) { // Iterator didn't use the stack so indicate that with MP_OBJ_NULL. sp[-MP_OBJ_ITER_BUF_NSLOTS + 1] = MP_OBJ_NULL; @@ -825,6 +898,17 @@ unwind_jump:; if (value == MP_OBJ_STOP_ITERATION) { sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator ip += ulab; // jump to after for-block +#if NO_NLR + } else if (value == MP_OBJ_NULL) { + // raised an exception + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(MP_STATE_THREAD(active_exception)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + MP_STATE_THREAD(active_exception) = NULL; + sp -= MP_OBJ_ITER_BUF_NSLOTS; // pop the exhausted iterator + ip += ulab; // jump to after for-block + } else { + RAISE_IF(true); + } +#endif } else { PUSH(value); // push the next iteration value #if MICROPY_PY_SYS_SETTRACE @@ -850,6 +934,7 @@ unwind_jump:; DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_tuple(unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -858,6 +943,7 @@ unwind_jump:; DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_list(unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -865,13 +951,14 @@ unwind_jump:; MARK_EXC_IP_SELECTIVE(); DECODE_UINT; PUSH(mp_obj_new_dict(unum)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } ENTRY(MP_BC_STORE_MAP): MARK_EXC_IP_SELECTIVE(); sp -= 2; - mp_obj_dict_store(sp[0], sp[2], sp[1]); + RAISE_IF_NULL(mp_obj_dict_store(sp[0], sp[2], sp[1])); DISPATCH(); #if MICROPY_PY_BUILTINS_SET @@ -880,6 +967,7 @@ unwind_jump:; DECODE_UINT; sp -= unum - 1; SET_TOP(mp_obj_new_set(unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #endif @@ -895,6 +983,7 @@ unwind_jump:; mp_obj_t stop = POP(); mp_obj_t start = TOP(); SET_TOP(mp_obj_new_slice(start, stop, step)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } #endif @@ -921,7 +1010,7 @@ unwind_jump:; ENTRY(MP_BC_UNPACK_SEQUENCE): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; - mp_unpack_sequence(sp[0], unum, sp); + RAISE_IF_NULL(mp_unpack_sequence(sp[0], unum, sp)); sp += unum - 1; DISPATCH(); } @@ -929,7 +1018,7 @@ unwind_jump:; ENTRY(MP_BC_UNPACK_EX): { MARK_EXC_IP_SELECTIVE(); DECODE_UINT; - mp_unpack_ex(sp[0], unum, sp); + RAISE_IF_NULL(mp_unpack_ex(sp[0], unum, sp)); sp += (unum & 0xff) + ((unum >> 8) & 0xff); DISPATCH(); } @@ -986,18 +1075,23 @@ unwind_jump:; #if MICROPY_STACKLESS_STRICT deep_recursion_error: mp_raise_recursion_depth(); + RAISE_IF(true); #endif } else #endif { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_function_n_kw(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1017,8 +1111,7 @@ unwind_jump:; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_call_args_t out_args; - mp_call_prepare_args_n_kw_var(false, unum, sp, &out_args); - + RAISE_IF(mp_call_prepare_args_n_kw_var(false, unum, sp, &out_args)); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); #if !MICROPY_ENABLE_PYSTACK @@ -1038,12 +1131,16 @@ unwind_jump:; { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw_var(false, unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1077,12 +1174,16 @@ unwind_jump:; { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw(unum & 0xff, (unum >> 8) & 0xff, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1102,7 +1203,8 @@ unwind_jump:; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); mp_call_args_t out_args; - mp_call_prepare_args_n_kw_var(true, unum, sp, &out_args); +#warning no NULL test ? + RAISE_IF(mp_call_prepare_args_n_kw_var(true, unum, sp, &out_args)); mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(out_args.fun, out_args.n_args, out_args.n_kw, out_args.args); @@ -1123,12 +1225,16 @@ unwind_jump:; { new_state->prev = code_state; code_state = new_state; +#if NO_NLR +#else nlr_pop(); +#endif goto run_code_state; } } #endif SET_TOP(mp_call_method_n_kw_var(true, unum, sp)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1166,7 +1272,10 @@ unwind_jump:; } POP_EXC_BLOCK(); } +#if NO_NLR +#else nlr_pop(); +#endif code_state->sp = sp; assert(exc_sp == exc_stack - 1); MICROPY_VM_HOOK_RETURN @@ -1201,7 +1310,7 @@ unwind_jump:; } } if (obj == MP_OBJ_NULL) { - obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("no active exception to reraise")); + obj = mp_obj_new_exception_msg(&mp_type_RuntimeError, "no active exception to reraise"); } RAISE(obj); } @@ -1222,7 +1331,6 @@ unwind_jump:; ENTRY(MP_BC_YIELD_VALUE): yield: - nlr_pop(); code_state->ip = ip; code_state->sp = sp; code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp); @@ -1238,7 +1346,10 @@ unwind_jump:; mp_obj_t send_value = POP(); mp_obj_t t_exc = MP_OBJ_NULL; mp_obj_t ret_value; +#if NO_NLR +#else code_state->sp = sp; // Save sp because it's needed if mp_resume raises StopIteration +#endif if (inject_exc != MP_OBJ_NULL) { t_exc = inject_exc; inject_exc = MP_OBJ_NULL; @@ -1268,9 +1379,20 @@ unwind_jump:; DISPATCH(); } else { assert(ret_kind == MP_VM_RETURN_EXCEPTION); +#if NO_NLR + // Pop exhausted gen + sp--; + if (EXC_MATCH(ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { + // StopIteration inside yield from call means return a value of + // yield from, so inject exception's value as yield from's result + PUSH(mp_obj_exception_get_value(MP_OBJ_FROM_PTR(ret_value))); + DISPATCH(); + } +#else assert(!EXC_MATCH(ret_value, MP_OBJ_FROM_PTR(&mp_type_StopIteration))); // Pop exhausted gen sp--; +#endif RAISE(ret_value); } } @@ -1281,6 +1403,7 @@ unwind_jump:; DECODE_QSTR; mp_obj_t obj = POP(); SET_TOP(mp_import_name(qst, obj, TOP())); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1289,6 +1412,9 @@ unwind_jump:; MARK_EXC_IP_SELECTIVE(); DECODE_QSTR; mp_obj_t obj = mp_import_from(TOP(), qst); +#if NO_NLR + RAISE_IF_NULL(obj); +#endif PUSH(obj); DISPATCH(); } @@ -1314,6 +1440,7 @@ unwind_jump:; ENTRY(MP_BC_UNARY_OP_MULTI): MARK_EXC_IP_SELECTIVE(); SET_TOP(mp_unary_op(ip[-1] - MP_BC_UNARY_OP_MULTI, TOP())); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); ENTRY(MP_BC_BINARY_OP_MULTI): { @@ -1321,6 +1448,7 @@ unwind_jump:; mp_obj_t rhs = POP(); mp_obj_t lhs = TOP(); SET_TOP(mp_binary_op(ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); } @@ -1331,26 +1459,34 @@ unwind_jump:; if (ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + MP_BC_LOAD_CONST_SMALL_INT_MULTI_NUM) { PUSH(MP_OBJ_NEW_SMALL_INT((mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - MP_BC_LOAD_CONST_SMALL_INT_MULTI_EXCESS)); DISPATCH(); + } else if (ip[-1] < MP_BC_LOAD_FAST_MULTI + MP_BC_LOAD_FAST_MULTI_NUM) { obj_shared = fastn[MP_BC_LOAD_FAST_MULTI - (mp_int_t)ip[-1]]; goto load_check; + } else if (ip[-1] < MP_BC_STORE_FAST_MULTI + MP_BC_STORE_FAST_MULTI_NUM) { fastn[MP_BC_STORE_FAST_MULTI - (mp_int_t)ip[-1]] = POP(); DISPATCH(); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_BC_UNARY_OP_MULTI_NUM) { SET_TOP(mp_unary_op(ip[-1] - MP_BC_UNARY_OP_MULTI, TOP())); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); + } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BC_BINARY_OP_MULTI_NUM) { - mp_obj_t rhs = POP(); + mp_obj_t rhs = POP(); // XXX may now be unreachable mp_obj_t lhs = TOP(); SET_TOP(mp_binary_op(ip[-1] - MP_BC_BINARY_OP_MULTI, lhs, rhs)); + RAISE_IF_NULL(TEST_TOP()); DISPATCH(); + } else #endif { - - mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NotImplementedError, MP_ERROR_TEXT("opcode")); + mp_obj_t obj = mp_obj_new_exception_msg(&mp_type_NotImplementedError, "opcode"); +#if NO_NLR +#else nlr_pop(); +#endif code_state->state[0] = obj; FRAME_LEAVE(); return MP_VM_RETURN_EXCEPTION; @@ -1366,23 +1502,18 @@ unwind_jump:; #if MICROPY_ENABLE_SCHEDULER // This is an inlined variant of mp_handle_pending if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { + MARK_EXC_IP_SELECTIVE(); mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); - // Re-check state is still pending now that we're in the atomic section. - if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { - MARK_EXC_IP_SELECTIVE(); - mp_obj_t obj = MP_STATE_VM(mp_pending_exception); - if (obj != MP_OBJ_NULL) { - MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; - if (!mp_sched_num_pending()) { - MP_STATE_VM(sched_state) = MP_SCHED_IDLE; - } - MICROPY_END_ATOMIC_SECTION(atomic_state); - RAISE(obj); + mp_obj_t obj = MP_STATE_VM(mp_pending_exception); + if (obj != MP_OBJ_NULL) { + MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL; + if (!mp_sched_num_pending()) { + MP_STATE_VM(sched_state) = MP_SCHED_IDLE; } - mp_handle_pending_tail(atomic_state); - } else { MICROPY_END_ATOMIC_SECTION(atomic_state); + RAISE(obj); } + mp_handle_pending_tail(atomic_state); } #else // This is an inlined variant of mp_handle_pending @@ -1415,19 +1546,35 @@ unwind_jump:; } // for loop +#if !NO_NLR } else { exception_handler: // exception occurred +#else + } + { +exception_handler: + // exception occurred + assert(MP_STATE_THREAD(active_exception) != NULL); - #if MICROPY_PY_SYS_EXC_INFO + // clear exception because we caught it + mp_obj_base_t *the_exc = MP_STATE_THREAD(active_exception); + MP_STATE_THREAD(active_exception) = NULL; +#endif + #if MICROPY_PY_SYS_EXC_INFO +#if !NO_NLR MP_STATE_VM(cur_exception) = nlr.ret_val; - #endif +#else + MP_STATE_VM(cur_exception) = the_exc; +#endif + #endif #if SELECTIVE_EXC_IP // with selective ip, we store the ip 1 byte past the opcode, so move ptr back code_state->ip -= 1; #endif +#if !NO_NLR if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) { if (code_state->ip) { // check if it's a StopIteration within a for block @@ -1454,6 +1601,15 @@ unwind_jump:; TRACE_TICK(code_state->ip, code_state->sp, true /* yes, it's an exception */); } #endif +#else + #if MICROPY_PY_SYS_SETTRACE + // Exceptions are traced here + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)the_exc)->type), MP_OBJ_FROM_PTR(&mp_type_Exception))) { + TRACE_TICK(code_state->ip, code_state->sp, true /* yes, it's an exception */); + } + #endif +#endif + #if MICROPY_STACKLESS unwind_loop: @@ -1462,7 +1618,11 @@ unwind_jump:; // - constant GeneratorExit object, because it's const // - exceptions re-raised by END_FINALLY // - exceptions re-raised explicitly by "raise" - if (nlr.ret_val != &mp_const_GeneratorExit_obj +#if !NO_NLR + if (THE_EXC != &mp_const_GeneratorExit_obj +#else + if (THE_EXC != &mp_const_GeneratorExit_obj.base +#endif && *code_state->ip != MP_BC_END_FINALLY && *code_state->ip != MP_BC_RAISE_LAST) { const byte *ip = code_state->fun_bc->bytecode; @@ -1485,7 +1645,7 @@ unwind_jump:; ip = mp_decode_uint_skip(ip); #endif size_t source_line = mp_bytecode_get_source_line(ip, bc); - mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(nlr.ret_val), source_file, source_line, block_name); + mp_obj_exception_add_traceback(MP_OBJ_FROM_PTR(THE_EXC), source_file, source_line, block_name); } while (exc_sp >= exc_stack && exc_sp->handler <= code_state->ip) { @@ -1506,9 +1666,9 @@ unwind_jump:; code_state->ip = exc_sp->handler; mp_obj_t *sp = MP_TAGPTR_PTR(exc_sp->val_sp); // save this exception in the stack so it can be used in a reraise, if needed - exc_sp->prev_exc = nlr.ret_val; + exc_sp->prev_exc = THE_EXC; // push exception object so it can be handled by bytecode - PUSH(MP_OBJ_FROM_PTR(nlr.ret_val)); + PUSH(MP_OBJ_FROM_PTR(THE_EXC)); code_state->sp = sp; #if MICROPY_STACKLESS @@ -1534,7 +1694,7 @@ unwind_jump:; } else { // propagate exception to higher level // Note: ip and sp don't have usable values at this point - code_state->state[0] = MP_OBJ_FROM_PTR(nlr.ret_val); // put exception here because sp is invalid + code_state->state[0] = MP_OBJ_FROM_PTR(THE_EXC); // put exception here because sp is invalid FRAME_LEAVE(); return MP_VM_RETURN_EXCEPTION; } diff --git a/py/vmentrytable.h b/py/vmentrytable.h index 068214bf9..791227087 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 diff --git a/py/vstr.c b/py/vstr.c index 56327b5f7..1893c6369 100644 --- a/py/vstr.c +++ b/py/vstr.c @@ -34,6 +34,9 @@ #include "py/runtime.h" #include "py/mpprint.h" +#pragma message "missing fwd decl of mp_vprintf" +extern int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args); + // returned value is always at least 1 greater than argument #define ROUND_ALLOC(a) (((a) & ((~0U) - 7)) + 8) @@ -61,13 +64,21 @@ void vstr_init_fixed_buf(vstr_t *vstr, size_t alloc, char *buf) { vstr->buf = buf; vstr->fixed_buf = true; } +#if NO_NLR +void void_add_strn(vstr_t *vstr, const char *str, size_t len); +void vstr_init_print(vstr_t *vstr, size_t alloc, mp_print_t *print) { + vstr_init(vstr, alloc); + print->data = vstr; + print->print_strn = (mp_print_strn_t)void_add_strn; +} +#else void vstr_init_print(vstr_t *vstr, size_t alloc, mp_print_t *print) { vstr_init(vstr, alloc); print->data = vstr; print->print_strn = (mp_print_strn_t)vstr_add_strn; } - +#endif void vstr_clear(vstr_t *vstr) { if (!vstr->fixed_buf) { m_del(char, vstr->buf, vstr->alloc); @@ -95,7 +106,12 @@ char *vstr_extend(vstr_t *vstr, size_t size) { if (vstr->fixed_buf) { // We can't reallocate, and the caller is expecting the space to // be there, so the only safe option is to raise an exception. +#if NO_NLR + mp_raise_msg_o(&mp_type_RuntimeError, NULL); + return NULL; +#else mp_raise_msg(&mp_type_RuntimeError, NULL); +#endif } char *new_buf = m_renew(char, vstr->buf, vstr->alloc, vstr->alloc + size); char *p = new_buf + vstr->alloc; @@ -104,18 +120,34 @@ char *vstr_extend(vstr_t *vstr, size_t size) { return p; } +#if NO_NLR +STATIC int vstr_ensure_extra(vstr_t *vstr, size_t size) { + if (vstr->buf == NULL) { + // could not allocate a buffer + return -1; + } +#else STATIC void vstr_ensure_extra(vstr_t *vstr, size_t size) { +#endif if (vstr->len + size > vstr->alloc) { if (vstr->fixed_buf) { // We can't reallocate, and the caller is expecting the space to // be there, so the only safe option is to raise an exception. +#if NO_NLR + mp_raise_msg_o(&mp_type_RuntimeError, NULL); + return -1; +#else mp_raise_msg(&mp_type_RuntimeError, NULL); +#endif } size_t new_alloc = ROUND_ALLOC((vstr->len + size) + 16); char *new_buf = m_renew(char, vstr->buf, vstr->alloc, new_alloc); vstr->alloc = new_alloc; vstr->buf = new_buf; } +#if NO_NLR + return 0; +#endif } void vstr_hint_size(vstr_t *vstr, size_t size) { @@ -133,7 +165,13 @@ char *vstr_add_len(vstr_t *vstr, size_t len) { char *vstr_null_terminated_str(vstr_t *vstr) { // If there's no more room, add single byte if (vstr->alloc == vstr->len) { +#if NO_NLR + if (vstr_extend(vstr, 1) == NULL) { + return NULL; + } +#else vstr_extend(vstr, 1); +#endif } vstr->buf[vstr->len] = '\0'; return vstr->buf; @@ -172,7 +210,34 @@ void vstr_add_char(vstr_t *vstr, unichar c) { vstr_add_byte(vstr, c); #endif } +#if NO_NLR + +#pragma message "NEED FIX TO CLANG WASM PRINT FUNCTION PTR" + +int vstr_add_str(vstr_t *vstr, const char *str) { + return vstr_add_strn(vstr, str, strlen(str)); +} + +int vstr_add_strn(vstr_t *vstr, const char *str, size_t len) { + if (vstr_ensure_extra(vstr, len)) { + return -1; + } + memmove(vstr->buf + vstr->len, str, len); + vstr->len += len; + return 0; // success +} + +void void_add_strn(vstr_t *vstr, const char *str, size_t len) { + if (vstr_ensure_extra(vstr, len)) { + //return -1; + } + memmove(vstr->buf + vstr->len, str, len); + vstr->len += len; +// return 0; // success +} + +#else void vstr_add_str(vstr_t *vstr, const char *str) { vstr_add_strn(vstr, str, strlen(str)); } @@ -182,6 +247,7 @@ void vstr_add_strn(vstr_t *vstr, const char *str, size_t len) { memmove(vstr->buf + vstr->len, str, len); vstr->len += len; } +#endif STATIC char *vstr_ins_blank_bytes(vstr_t *vstr, size_t byte_pos, size_t byte_len) { size_t l = vstr->len; @@ -233,6 +299,15 @@ void vstr_cut_out_bytes(vstr_t *vstr, size_t byte_pos, size_t bytes_to_cut) { } } +void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap) { +#if NO_NLR + mp_print_t print = {vstr, (mp_print_strn_t)void_add_strn}; +#else + mp_print_t print = {vstr, (mp_print_strn_t)vstr_add_strn}; +#endif + mp_vprintf(&print, fmt, ap); +} + void vstr_printf(vstr_t *vstr, const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -240,7 +315,3 @@ void vstr_printf(vstr_t *vstr, const char *fmt, ...) { va_end(ap); } -void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap) { - mp_print_t print = {vstr, (mp_print_strn_t)vstr_add_strn}; - mp_vprintf(&print, fmt, ap); -} diff --git a/tests/basics/class_dict.py b/tests/basics/class_dict.py index f80ded678..508ae5e2e 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) diff --git a/tests/basics/containment.py b/tests/basics/containment.py index 4c94a9bae..24317ddd2 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/basics/fun_globals.py b/tests/basics/fun_globals.py new file mode 100644 index 000000000..3f32e8bdb --- /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") diff --git a/tests/basics/int_big_div.py b/tests/basics/int_big_div.py index 642f051d4..29fd40597 100644 --- a/tests/basics/int_big_div.py +++ b/tests/basics/int_big_div.py @@ -8,3 +8,7 @@ print((x + 1) // x) x = 0x86c60128feff5330 print((x + 1) // x) + +# these check edge cases where borrow overflows +print((2 ** 48 - 1) ** 2 // (2 ** 48 - 1)) +print((2 ** 256 - 2 ** 32) ** 2 // (2 ** 256 - 2 ** 32)) diff --git a/tests/basics/struct1_intbig.py b/tests/basics/struct1_intbig.py index 380293f36..24541c8a4 100644 --- a/tests/basics/struct1_intbig.py +++ b/tests/basics/struct1_intbig.py @@ -36,3 +36,11 @@ # check small int overflow print(struct.unpack("": + for type_ in "bhiq": + fmt = endian + type_ + b = struct.pack(fmt, -2 + bigzero) + print(fmt, b, struct.unpack(fmt, b)) diff --git a/tests/cmdline/cmd_parsetree.py.exp b/tests/cmdline/cmd_parsetree.py.exp index 42a8228fb..e64f4f782 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) ---------------- diff --git a/tests/esp32/esp32_nvs.py b/tests/esp32/esp32_nvs.py new file mode 100644 index 000000000..fd8b152ca --- /dev/null +++ b/tests/esp32/esp32_nvs.py @@ -0,0 +1,67 @@ +# Test the esp32 NVS class - access to esp-idf's Non-Volatile-Storage + +from esp32 import NVS + +nvs = NVS("mp-test") + +# test setting and gettin an integer kv +nvs.set_i32("key1", 1234) +print(nvs.get_i32("key1")) +nvs.set_i32("key2", -503) +print(nvs.get_i32("key2")) +print(nvs.get_i32("key1")) + +# test setting and getting a blob kv using a bytearray +blob1 = "testing a string as a blob" +nvs.set_blob("blob1", blob1) +buf1 = bytearray(len(blob1)) +len1 = nvs.get_blob("blob1", buf1) +print(buf1) +print(len(blob1), len1) + +# test setting and getting a blob kv using a string +blob2 = b"testing a bytearray" +nvs.set_blob("blob2", blob2) +buf2 = bytearray(len(blob2)) +len2 = nvs.get_blob("blob2", buf2) +print(buf2) +print(len(blob2), len2) + +# test raising of error exceptions +nvs.erase_key("key1") +try: + nvs.erase_key("key1") # not found +except OSError as e: + print(e) +try: + nvs.get_i32("key1") # not found +except OSError as e: + print(e) +try: + nvs.get_i32("blob1") # not found (blob1 exists but diff type) +except OSError as e: + print(e) +try: + buf3 = bytearray(10) + nvs.get_blob("blob1", buf3) # invalid length (too short) +except OSError as e: + print(e) + +nvs.commit() # we're not verifying that this does anything, just doesn't error + +# test using a second namespace and that it doesn't interfere with first +nvs2 = NVS("mp-test2") +try: + print(nvs2.get_i32("key2")) +except OSError as e: + print(e) +nvs2.set_i32("key2", 7654) +print(nvs.get_i32("key2")) +print(nvs2.get_i32("key2")) + +# clean-up (the namespaces will remain) +nvs.erase_key("key2") +nvs.erase_key("blob1") +nvs.erase_key("blob2") +nvs2.erase_key("key2") +nvs.commit() diff --git a/tests/esp32/esp32_nvs.py.exp b/tests/esp32/esp32_nvs.py.exp new file mode 100644 index 000000000..33cdfd6df --- /dev/null +++ b/tests/esp32/esp32_nvs.py.exp @@ -0,0 +1,14 @@ +1234 +-503 +1234 +bytearray(b'testing a string as a blob') +26 26 +bytearray(b'testing a bytearray') +19 19 +(-4354, 'ESP_ERR_NVS_NOT_FOUND') +(-4354, 'ESP_ERR_NVS_NOT_FOUND') +(-4354, 'ESP_ERR_NVS_NOT_FOUND') +(-4364, 'ESP_ERR_NVS_INVALID_LENGTH') +(-4354, 'ESP_ERR_NVS_NOT_FOUND') +-503 +7654 diff --git a/tests/extmod/machine1.py b/tests/extmod/machine1.py index 6ff38cc05..0c7f8122f 100644 --- a/tests/extmod/machine1.py +++ b/tests/extmod/machine1.py @@ -26,3 +26,23 @@ del machine.mem8[0] except TypeError: print("TypeError") + +try: + machine.mem8[0:1] +except TypeError: + print("TypeError") + +try: + machine.mem8[0:1] = 10 +except TypeError: + print("TypeError") + +try: + machine.mem8["hello"] +except TypeError: + print("TypeError") + +try: + machine.mem8["hello"] = 10 +except TypeError: + print("TypeError") diff --git a/tests/extmod/machine1.py.exp b/tests/extmod/machine1.py.exp index bb421ea5c..250485969 100644 --- a/tests/extmod/machine1.py.exp +++ b/tests/extmod/machine1.py.exp @@ -2,3 +2,7 @@ ValueError ValueError TypeError +TypeError +TypeError +TypeError +TypeError diff --git a/tests/extmod/uasyncio_current_task.py b/tests/extmod/uasyncio_current_task.py new file mode 100644 index 000000000..3165a2cf1 --- /dev/null +++ b/tests/extmod/uasyncio_current_task.py @@ -0,0 +1,25 @@ +# Test current_task() function + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(result): + result[0] = asyncio.current_task() + + +async def main(): + result = [None] + t = asyncio.create_task(task(result)) + await asyncio.sleep(0) + await asyncio.sleep(0) + print(t is result[0]) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_current_task.py.exp b/tests/extmod/uasyncio_current_task.py.exp new file mode 100644 index 000000000..0ca95142b --- /dev/null +++ b/tests/extmod/uasyncio_current_task.py.exp @@ -0,0 +1 @@ +True diff --git a/tests/extmod/uasyncio_set_exception_handler.py b/tests/extmod/uasyncio_set_exception_handler.py index ad62a79b7..fe7b83eb4 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 4744641e5..fb4711469 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 diff --git a/tests/extmod/uasyncio_task_done.py b/tests/extmod/uasyncio_task_done.py new file mode 100644 index 000000000..2700da8c3 --- /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 000000000..ddda04c5e --- /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 diff --git a/tests/extmod/uasyncio_threadsafeflag.py b/tests/extmod/uasyncio_threadsafeflag.py new file mode 100644 index 000000000..4e002a3d2 --- /dev/null +++ b/tests/extmod/uasyncio_threadsafeflag.py @@ -0,0 +1,79 @@ +# Test Event class + +try: + import uasyncio as asyncio +except ImportError: + print("SKIP") + raise SystemExit + + +import micropython + +try: + micropython.schedule +except AttributeError: + print("SKIP") + raise SystemExit + + +try: + # Unix port can't select/poll on user-defined types. + import uselect as select + + poller = select.poll() + poller.register(asyncio.ThreadSafeFlag()) +except TypeError: + print("SKIP") + raise SystemExit + + +async def task(id, flag): + print("task", id) + await flag.wait() + print("task", id, "done") + + +def set_from_schedule(flag): + print("schedule") + flag.set() + print("schedule done") + + +async def main(): + flag = asyncio.ThreadSafeFlag() + + # Set the flag from within the loop. + t = asyncio.create_task(task(1, flag)) + print("yield") + await asyncio.sleep(0) + print("set event") + flag.set() + print("yield") + await asyncio.sleep(0) + print("wait task") + await t + + # Set the flag from scheduler context. + print("----") + t = asyncio.create_task(task(2, flag)) + print("yield") + await asyncio.sleep(0) + print("set event") + micropython.schedule(set_from_schedule, flag) + print("yield") + await asyncio.sleep(0) + print("wait task") + await t + + # Flag already set. + print("----") + print("set event") + flag.set() + t = asyncio.create_task(task(3, flag)) + print("yield") + await asyncio.sleep(0) + print("wait task") + await t + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_threadsafeflag.py.exp b/tests/extmod/uasyncio_threadsafeflag.py.exp new file mode 100644 index 000000000..aef4e479b --- /dev/null +++ b/tests/extmod/uasyncio_threadsafeflag.py.exp @@ -0,0 +1,21 @@ +yield +task 1 +set event +yield +wait task +task 1 done +---- +yield +task 2 +set event +yield +schedule +schedule done +wait task +task 2 done +---- +set event +yield +task 3 +task 3 done +wait task diff --git a/tests/extmod/uasyncio_wait_for.py b/tests/extmod/uasyncio_wait_for.py index 92fd174b8..9612d1620 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 41a5f2481..a4201d31f 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 000000000..33738085c --- /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 000000000..9f22f1a7d --- /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/extmod/uctypes_array_load_store.py b/tests/extmod/uctypes_array_load_store.py new file mode 100644 index 000000000..3b9bb6d73 --- /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 = 5 + +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 - 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 new file mode 100644 index 000000000..10de80464 --- /dev/null +++ b/tests/extmod/uctypes_array_load_store.py.exp @@ -0,0 +1,24 @@ +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 diff --git a/tests/extmod/urandom_seed_default.py b/tests/extmod/urandom_seed_default.py new file mode 100644 index 000000000..a032b9362 --- /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()) diff --git a/tests/extmod/ure_sub.py b/tests/extmod/ure_sub.py index 953e7bf62..ae6ad28d6 100644 --- a/tests/extmod/ure_sub.py +++ b/tests/extmod/ure_sub.py @@ -43,6 +43,9 @@ def A(): ) ) +# \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 @@ def A(): re.sub(123, "a", "a") except TypeError: print("TypeError") + +# Include \ in the sub replacement +print(re.sub("b", "\\\\b", "abc")) diff --git a/tests/extmod/utime_res.py b/tests/extmod/utime_res.py new file mode 100644 index 000000000..4b6243348 --- /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 000000000..08c2d8295 --- /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 diff --git a/tests/extmod/utime_time_ns.py b/tests/extmod/utime_time_ns.py new file mode 100644 index 000000000..0d13f839d --- /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(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 bounds (2ms to 50ms). +if 2000000 < 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 000000000..dbde42265 --- /dev/null +++ b/tests/extmod/utime_time_ns.py.exp @@ -0,0 +1,2 @@ +True +True diff --git a/tests/extmod/vfs_lfs_mount.py b/tests/extmod/vfs_lfs_mount.py index 2c40b2989..bea8a2723 100644 --- a/tests/extmod/vfs_lfs_mount.py +++ b/tests/extmod/vfs_lfs_mount.py @@ -16,12 +16,12 @@ class RAMBlockDevice: def __init__(self, blocks): self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) - def readblocks(self, block, buf, off): + def readblocks(self, block, buf, off=0): addr = block * self.ERASE_BLOCK_SIZE + off for i in range(len(buf)): buf[i] = self.data[addr + i] - def writeblocks(self, block, buf, off): + def writeblocks(self, block, buf, off=0): addr = block * self.ERASE_BLOCK_SIZE + off for i in range(len(buf)): self.data[addr + i] = buf[i] @@ -35,9 +35,17 @@ def ioctl(self, op, arg): return 0 -def test(bdev, vfs_class): +def test(vfs_class): print("test", vfs_class) + bdev = RAMBlockDevice(30) + + # mount bdev unformatted + try: + uos.mount(bdev, "/lfs") + except Exception as er: + print(repr(er)) + # mkfs vfs_class.mkfs(bdev) @@ -67,12 +75,33 @@ 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") + + # mount bdev again + uos.mount(bdev, "/lfs") + + # umount + uos.umount("/lfs") + # clear imported modules usys.modules.clear() -bdev = RAMBlockDevice(30) - # initialise path import usys @@ -81,5 +110,5 @@ def test(bdev, vfs_class): usys.path.append("") # run tests -test(bdev, uos.VfsLfs1) -test(bdev, uos.VfsLfs2) +test(uos.VfsLfs1) +test(uos.VfsLfs2) diff --git a/tests/extmod/vfs_lfs_mount.py.exp b/tests/extmod/vfs_lfs_mount.py.exp index b5c521531..68561b480 100644 --- a/tests/extmod/vfs_lfs_mount.py.exp +++ b/tests/extmod/vfs_lfs_mount.py.exp @@ -1,8 +1,16 @@ test +OSError(19,) hello from lfs package hello from lfs +lfsmod2.py: print("hello from lfs") + +OSError(30,) test +OSError(19,) hello from lfs package hello from lfs +lfsmod2.py: print("hello from lfs") + +OSError(36,) diff --git a/tests/extmod/vfs_lfs_superblock.py b/tests/extmod/vfs_lfs_superblock.py new file mode 100644 index 000000000..1ac567555 --- /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 000000000..c71bf50e8 --- /dev/null +++ b/tests/extmod/vfs_lfs_superblock.py.exp @@ -0,0 +1,2 @@ +[] +[] diff --git a/tests/extmod/vfs_posix.py b/tests/extmod/vfs_posix.py new file mode 100644 index 000000000..f8c4aae40 --- /dev/null +++ b/tests/extmod/vfs_posix.py @@ -0,0 +1,88 @@ +# Test for VfsPosix + +try: + import uos + + uos.VfsPosix +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +# We need a directory for testing that doesn't already exist. +# Skip the test if it does exist. +temp_dir = "micropy_test_dir" +try: + uos.stat(temp_dir) + print("SKIP") + raise SystemExit +except OSError: + pass + +# 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("/"))) + +# mkdir +uos.mkdir(temp_dir) + +# file create +f = open(temp_dir + "/test", "w") +f.write("hello") +f.close() + +# close on a closed file should succeed +f.close() + +# construct a file object using the type constructor, with a raw fileno +f = type(f)(2) +print(f) + +# file read +f = open(temp_dir + "/test", "r") +print(f.read()) +f.close() + +# rename +uos.rename(temp_dir + "/test", temp_dir + "/test2") +print(uos.listdir(temp_dir)) + +# construct new VfsPosix with path argument +vfs = uos.VfsPosix(temp_dir) +print(list(i[0] for i in vfs.ilistdir("."))) + +# stat, statvfs +print(type(vfs.stat("."))) +print(type(vfs.statvfs("."))) + +# check types of ilistdir with str/bytes arguments +print(type(list(vfs.ilistdir("."))[0][0])) +print(type(list(vfs.ilistdir(b"."))[0][0])) + +# remove +uos.remove(temp_dir + "/test2") +print(uos.listdir(temp_dir)) + +# remove with error +try: + uos.remove(temp_dir + "/test2") +except OSError: + print("remove OSError") + +# rmdir +uos.rmdir(temp_dir) +print(temp_dir in uos.listdir()) + +# rmdir with error +try: + uos.rmdir(temp_dir) +except OSError: + print("rmdir OSError") diff --git a/tests/extmod/vfs_posix.py.exp b/tests/extmod/vfs_posix.py.exp new file mode 100644 index 000000000..e7d68f38e --- /dev/null +++ b/tests/extmod/vfs_posix.py.exp @@ -0,0 +1,16 @@ +/ +True + + + +hello +['test2'] +['test2'] + + + + +[] +remove OSError +False +rmdir OSError diff --git a/tests/io/file1.py b/tests/io/file1.py index 2a46c9c63..de30045d3 100644 --- a/tests/io/file1.py +++ b/tests/io/file1.py @@ -44,3 +44,6 @@ except OSError: print("OSError") f.close() + +# close() on a closed file +f.close() diff --git a/tests/micropython/extreme_exc.py b/tests/micropython/extreme_exc.py index dae5b1518..9d2f24745 100644 --- a/tests/micropython/extreme_exc.py +++ b/tests/micropython/extreme_exc.py @@ -126,7 +126,7 @@ def f(): ) except Exception as er: e = er - lst[0] = None + lst[0][0] = None lst = None print(repr(e)[:10]) diff --git a/tests/micropython/native_for.py b/tests/micropython/native_for.py new file mode 100644 index 000000000..c640a8d08 --- /dev/null +++ b/tests/micropython/native_for.py @@ -0,0 +1,19 @@ +# test for native for loops + + +@micropython.native +def f1(n): + for i in range(n): + print(i) + + +f1(4) + + +@micropython.native +def f2(r): + for i in r: + print(i) + + +f2(range(4)) diff --git a/tests/micropython/native_for.py.exp b/tests/micropython/native_for.py.exp new file mode 100644 index 000000000..d4dc73eff --- /dev/null +++ b/tests/micropython/native_for.py.exp @@ -0,0 +1,8 @@ +0 +1 +2 +3 +0 +1 +2 +3 diff --git a/tests/misc/sys_settrace_features.py b/tests/misc/sys_settrace_features.py index a12304489..8a38b7534 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, ) ) @@ -60,7 +60,9 @@ 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: + to_ignore = ["importlib", "zipimport", "encodings"] + frame_name = frame.f_globals["__name__"] + if any(name in frame_name for name in to_ignore): return print("### trace_handler::main event:", event) @@ -94,9 +96,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 4ace0f50e..43065df4a 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 de9d0bf1c..a83450afe 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 06d0dc17b..1186bd91a 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 f56f98fae..ff9ef577c 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/sys_settrace_generic.py b/tests/misc/sys_settrace_subdir/sys_settrace_generic.py new file mode 100644 index 000000000..a60ca955d --- /dev/null +++ b/tests/misc/sys_settrace_subdir/sys_settrace_generic.py @@ -0,0 +1,92 @@ +print("Now comes the language constructions tests.") + +# function +def test_func(): + def test_sub_func(): + print("test_function") + + test_sub_func() + + +# closure +def test_closure(msg): + def make_closure(): + print(msg) + + return make_closure + + +# exception +def test_exception(): + try: + raise Exception("test_exception") + + except Exception: + pass + + finally: + pass + + +# listcomp +def test_listcomp(): + print("test_listcomp", [x for x in range(3)]) + + +# lambda +def test_lambda(): + func_obj_1 = lambda a, b: a + b + print(func_obj_1(10, 20)) + + +# import +def test_import(): + from sys_settrace_subdir import sys_settrace_importme + + sys_settrace_importme.dummy() + sys_settrace_importme.saysomething() + + +# class +class TLClass: + def method(): + pass + + pass + + +def test_class(): + class TestClass: + __anynum = -9 + + def method(self): + print("test_class_method") + self.__anynum += 1 + + def prprty_getter(self): + return self.__anynum + + def prprty_setter(self, what): + self.__anynum = what + + prprty = property(prprty_getter, prprty_setter) + + cls = TestClass() + cls.method() + print("test_class_property", cls.prprty) + cls.prprty = 12 + print("test_class_property", cls.prprty) + + +def run_tests(): + test_func() + test_closure_inst = test_closure("test_closure") + test_closure_inst() + test_exception() + test_listcomp() + test_lambda() + test_class() + test_import() + + +print("And it's done!") diff --git a/tests/misc/sys_settrace_subdir/sys_settrace_importme.py b/tests/misc/sys_settrace_subdir/sys_settrace_importme.py new file mode 100644 index 000000000..de561ef21 --- /dev/null +++ b/tests/misc/sys_settrace_subdir/sys_settrace_importme.py @@ -0,0 +1,28 @@ +print("Yep, I got imported.") + +try: + x = const(1) +except NameError: + print("const not defined") + +const = lambda x: x + +_CNT01 = "CONST01" +_CNT02 = const(123) +A123 = const(123) +a123 = const(123) + + +def dummy(): + return False + + +def saysomething(): + print("There, I said it.") + + +def neverexecuted(): + print("Never got here!") + + +print("Yep, got here") diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index d918d9aef..327822a01 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -31,60 +31,55 @@ ) 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", 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]) - 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: + return waiting_events.pop(event) machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -99,39 +94,35 @@ def instance0(): ble.gatts_write(char_handle, "periph0") # Wait for central to connect to us. - if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): - return - conn_handle, _, _ = waiting_data + conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - # Wait for a write to the characteristic from the central. - wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + # A - # Wait a bit, then write the characteristic and notify it. - time.sleep_ms(1000) + # Wait for a write to the characteristic from the central, + # then reply with a notification. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) print("gatts_write") ble.gatts_write(char_handle, "periph1") print("gatts_notify") ble.gatts_notify(conn_handle, char_handle) - # Wait for a write to the characteristic from the central. - wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + # B - # Wait a bit, then notify a new value on the characteristic. - time.sleep_ms(1000) + # Wait for a write to the characteristic from the central, + # then reply with value-included notification. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) print("gatts_notify") ble.gatts_notify(conn_handle, char_handle, "periph2") - # Wait for a write to the characteristic from the central. - wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + # C - # Wait a bit, then notify a new value on the characteristic. - time.sleep_ms(1000) + # Wait for a write to the characteristic from the central, + # then reply with an indication. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) print("gatts_write") ble.gatts_write(char_handle, "periph3") print("gatts_indicate") ble.gatts_indicate(conn_handle, char_handle) - - # Wait for the indicate ack. wait_for_event(_IRQ_GATTS_INDICATE_DONE, TIMEOUT_MS) # Wait for the central to disconnect. @@ -147,49 +138,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 90dc46c06..31667415e 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 bb6ff8e42..bb1ef94a7 100644 --- a/tests/multi_bluetooth/ble_gap_advertise.py +++ b/tests/multi_bluetooth/ble_gap_advertise.py @@ -46,7 +46,10 @@ def irq(ev, data): elif ev == _IRQ_SCAN_DONE: finished = True - ble.config(rxbuf=2000) + try: + ble.config(rxbuf=2000) + except: + pass ble.irq(irq) ble.gap_scan(2 * ADV_TIME_S * 1000, 10000, 10000) while not finished: diff --git a/tests/multi_bluetooth/ble_gap_connect.py b/tests/multi_bluetooth/ble_gap_connect.py index 8b40a2916..95d1be7ba 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -10,101 +10,75 @@ _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: + return waiting_events.pop(event) 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 92ea94278..556068114 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -16,45 +16,42 @@ 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", 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: - 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: + return waiting_events.pop(event) machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -79,10 +76,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 +86,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 +94,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 +111,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 0f4ee6e3d..a7a7aa367 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_gap_pair.py b/tests/multi_bluetooth/ble_gap_pair.py new file mode 100644 index 000000000..728513405 --- /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 000000000..9efe0fb22 --- /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 000000000..a29c21788 --- /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 000000000..271403a71 --- /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 diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py b/tests/multi_bluetooth/ble_gatt_data_transfer.py index 240f04860..861cb49fc 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -29,35 +29,33 @@ SERVICE = (SERVICE_UUID, (CHAR_CTRL, CHAR_RX, CHAR_TX)) SERVICES = (SERVICE,) -waiting_event = None -waiting_data = None -ctrl_value_handle = 0 -rx_value_handle = 0 -tx_value_handle = 0 +waiting_events = {} def irq(event, data): - global waiting_event, waiting_data, ctrl_value_handle, rx_value_handle, tx_value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_CENTRAL_DISCONNECT: print("_IRQ_CENTRAL_DISCONNECT") elif event == _IRQ_GATTS_WRITE: print("_IRQ_GATTS_WRITE") + waiting_events[(event, data[1])] = None elif event == _IRQ_PERIPHERAL_CONNECT: print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: if data[-1] == CHAR_CTRL_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - ctrl_value_handle = data[2] + waiting_events[(event, CHAR_CTRL_UUID)] = data[2] elif data[-1] == CHAR_RX_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - rx_value_handle = data[2] + waiting_events[(event, CHAR_RX_UUID)] = data[2] elif data[-1] == CHAR_TX_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - tx_value_handle = data[2] + waiting_events[(event, CHAR_TX_UUID)] = data[2] elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_READ_RESULT: @@ -67,27 +65,20 @@ 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])) + 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: + return waiting_events.pop(event) machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -103,15 +94,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 +124,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 +146,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 57f95bf01..f6ee5fa00 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py @@ -24,45 +24,40 @@ ) 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: + return waiting_events.pop(event) machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -86,13 +81,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 7f4d8da81..d14f80237 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_l2cap.py b/tests/multi_bluetooth/ble_l2cap.py new file mode 100644 index 000000000..a26f59b3e --- /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 000000000..0c572d99f --- /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 diff --git a/tests/multi_bluetooth/ble_mtu.py b/tests/multi_bluetooth/ble_mtu.py index ef3199c5b..f202ec764 100644 --- a/tests/multi_bluetooth/ble_mtu.py +++ b/tests/multi_bluetooth/ble_mtu.py @@ -51,7 +51,6 @@ def irq(event, data): - global value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") waiting_events[event] = data[0] @@ -68,6 +67,8 @@ def irq(event, data): if data[-1] == CHAR_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) waiting_events[event] = data[2] + else: + return elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_WRITE_DONE: @@ -86,9 +87,9 @@ 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 + return waiting_events.pop(event) machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -101,8 +102,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 +115,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 +148,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 +158,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) diff --git a/tests/multi_bluetooth/perf_gatt_notify.py b/tests/multi_bluetooth/perf_gatt_notify.py new file mode 100644 index 000000000..88986dda3 --- /dev/null +++ b/tests/multi_bluetooth/perf_gatt_notify.py @@ -0,0 +1,133 @@ +# Ping-pong GATT notifications between two devices. + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 2000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_NOTIFY = const(18) + +# How long to run the test for. +_NUM_NOTIFICATIONS = const(50) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = ( + CHAR_UUID, + bluetooth.FLAG_NOTIFY, +) +SERVICE = ( + SERVICE_UUID, + (CHAR,), +) +SERVICES = (SERVICE,) + +is_central = False + +waiting_events = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_CONNECT: + waiting_events[event] = data[0] + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + # conn_handle, def_handle, value_handle, properties, uuid = data + if data[-1] == CHAR_UUID: + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_NOTIFY: + if is_central: + conn_handle, value_handle, notify_data = data + ble.gatts_notify(conn_handle, value_handle, b"central" + notify_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)) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services(SERVICES) + 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) + + # Discover characteristics. + 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) + + # Give the central enough time to discover chars. + time.sleep_ms(500) + + ticks_start = time.ticks_ms() + + for i in range(_NUM_NOTIFICATIONS): + # Send a notification and wait for a response. + ble.gatts_notify(conn_handle, value_handle, "peripheral" + str(i)) + wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) + + ticks_end = time.ticks_ms() + ticks_total = time.ticks_diff(ticks_end, ticks_start) + print( + "Acknowledged {} notifications in {} ms. {} ms/notification.".format( + _NUM_NOTIFICATIONS, ticks_total, ticks_total // _NUM_NOTIFICATIONS + ) + ) + + # Disconnect the central. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + global is_central + is_central = True + ((char_handle,),) = ble.gatts_register_services(SERVICES) + 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) + + # Discover characteristics. + 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) + + # The IRQ handler will respond to each notification. + + # Wait for the peripheral to disconnect us. + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, 20000) + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/perf_gatt_notify.py.exp b/tests/multi_bluetooth/perf_gatt_notify.py.exp new file mode 100644 index 000000000..e69de29bb diff --git a/tests/multi_bluetooth/perf_l2cap.py b/tests/multi_bluetooth/perf_l2cap.py new file mode 100644 index 000000000..9b07bb1dc --- /dev/null +++ b/tests/multi_bluetooth/perf_l2cap.py @@ -0,0 +1,148 @@ +# Send L2CAP data as fast as possible and time it. + +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_PSM = const(22) +_L2CAP_MTU = const(512) + +_PAYLOAD_LEN = const(_L2CAP_MTU) +_NUM_PAYLOADS = const(20) + +_RANDOM_SEED = 22 + + +waiting_events = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + conn_handle, addr_type, addr = data + waiting_events[event] = conn_handle + elif event == _IRQ_PERIPHERAL_CONNECT: + conn_handle, addr_type, addr = data + waiting_events[event] = conn_handle + elif event == _IRQ_L2CAP_ACCEPT: + conn_handle, cid, psm, our_mtu, peer_mtu = data + waiting_events[event] = (conn_handle, cid, psm) + elif event == _IRQ_L2CAP_CONNECT: + conn_handle, cid, psm, our_mtu, peer_mtu = data + waiting_events[event] = (conn_handle, cid, psm, our_mtu, peer_mtu) + + 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) + for i in range(_NUM_PAYLOADS): + for j in range(_PAYLOAD_LEN): + buf[j] = random.randint(0, 255) + if not ble.l2cap_send(conn_handle, cid, buf): + 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 + ticks_first_byte = 0 + while recv_bytes < expected_bytes: + wait_for_event(_IRQ_L2CAP_RECV, TIMEOUT_MS) + if not ticks_first_byte: + ticks_first_byte = time.ticks_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 + ticks_end = time.ticks_ms() + return recv_bytes, recv_correct, time.ticks_diff(ticks_end, ticks_first_byte) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + 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) + + 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) + + send_data(ble, conn_handle, cid) + + wait_for_event(_IRQ_L2CAP_DISCONNECT, TIMEOUT_MS) + + # 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. + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + 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) + + recv_bytes, recv_correct, total_ticks = recv_data(ble, conn_handle, cid) + + # Disconnect channel. + ble.l2cap_disconnect(conn_handle, cid) + wait_for_event(_IRQ_L2CAP_DISCONNECT, TIMEOUT_MS) + + print( + "Received {}/{} bytes in {} ms. {} B/s".format( + recv_bytes, recv_correct, total_ticks, recv_bytes * 1000 // total_ticks + ) + ) + + # Disconnect from peripheral. + 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/perf_l2cap.py.exp b/tests/multi_bluetooth/perf_l2cap.py.exp new file mode 100644 index 000000000..e69de29bb diff --git a/tests/multi_bluetooth/stress_log_filesystem.py b/tests/multi_bluetooth/stress_log_filesystem.py new file mode 100644 index 000000000..83df555ce --- /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 000000000..8e1144b78 --- /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 diff --git a/tests/net_hosted/accept_timeout.py b/tests/net_hosted/accept_timeout.py index ff989110a..5f528d557 100644 --- a/tests/net_hosted/accept_timeout.py +++ b/tests/net_hosted/accept_timeout.py @@ -1,9 +1,9 @@ # test that socket.accept() on a socket with timeout raises ETIMEDOUT try: - import usocket as socket + import uerrno as errno, usocket as socket except: - import socket + import errno, socket try: socket.socket.settimeout @@ -18,5 +18,5 @@ try: s.accept() except OSError as er: - print(er.args[0] in (110, "timed out")) # 110 is ETIMEDOUT; CPython uses a string + print(er.args[0] in (errno.ETIMEDOUT, "timed out")) # CPython uses a string instead of errno s.close() diff --git a/tests/net_hosted/connect_nonblock.py b/tests/net_hosted/connect_nonblock.py index 3a3eaa2ba..c024b65a0 100644 --- a/tests/net_hosted/connect_nonblock.py +++ b/tests/net_hosted/connect_nonblock.py @@ -2,8 +2,9 @@ try: import usocket as socket + import uerrno as errno except: - import socket + import socket, errno def test(peer_addr): @@ -12,7 +13,7 @@ def test(peer_addr): try: s.connect(peer_addr) except OSError as er: - print(er.args[0] == 115) # 115 is EINPROGRESS + print(er.args[0] == errno.EINPROGRESS) s.close() diff --git a/tests/net_hosted/connect_nonblock_xfer.py b/tests/net_hosted/connect_nonblock_xfer.py new file mode 100644 index 000000000..feb648ea0 --- /dev/null +++ b/tests/net_hosted/connect_nonblock_xfer.py @@ -0,0 +1,147 @@ +# test that socket.connect() on a non-blocking socket raises EINPROGRESS +# and that an immediate write/send/read/recv does the right thing + +try: + import sys, time + import uerrno as errno, usocket as socket, ussl as ssl +except: + import socket, errno, ssl +isMP = sys.implementation.name == "micropython" + + +def dp(e): + # uncomment next line for development and testing, to print the actual exceptions + # print(repr(e)) + pass + + +# do_connect establishes the socket and wraps it if tls is True. +# If handshake is true, the initial connect (and TLS handshake) is +# allowed to be performed before returning. +def do_connect(peer_addr, tls, handshake): + s = socket.socket() + s.setblocking(False) + try: + # print("Connecting to", peer_addr) + s.connect(peer_addr) + except OSError as er: + print("connect:", er.args[0] == errno.EINPROGRESS) + if er.args[0] != errno.EINPROGRESS: + print(" got", er.args[0]) + # wrap with ssl/tls if desired + if tls: + try: + if sys.implementation.name == "micropython": + s = ssl.wrap_socket(s, do_handshake=handshake) + else: + s = ssl.wrap_socket(s, do_handshake_on_connect=handshake) + print("wrap: True") + except Exception as e: + dp(e) + print("wrap:", e) + elif handshake: + # just sleep a little bit, this allows any connect() errors to happen + time.sleep(0.2) + return s + + +# test runs the test against a specific peer address. +def test(peer_addr, tls=False, handshake=False): + # MicroPython plain sockets have read/write, but CPython's don't + # MicroPython TLS sockets and CPython's have read/write + # hasRW captures this wonderful state of affairs + hasRW = isMP or tls + + # MicroPython plain sockets and CPython's have send/recv + # MicroPython TLS sockets don't have send/recv, but CPython's do + # hasSR captures this wonderful state of affairs + hasSR = not (isMP and tls) + + # connect + send + if hasSR: + s = do_connect(peer_addr, tls, handshake) + # send -> 4 or EAGAIN + try: + ret = s.send(b"1234") + print("send:", handshake and ret == 4) + except OSError as er: + # + dp(er) + print("send:", er.args[0] in (errno.EAGAIN, errno.EINPROGRESS)) + s.close() + else: # fake it... + print("connect:", True) + if tls: + print("wrap:", True) + print("send:", True) + + # connect + write + if hasRW: + s = do_connect(peer_addr, tls, handshake) + # write -> None + try: + ret = s.write(b"1234") + print("write:", ret in (4, None)) # SSL may accept 4 into buffer + except OSError as er: + dp(er) + print("write:", False) # should not raise + except ValueError as er: # CPython + dp(er) + print("write:", er.args[0] == "Write on closed or unwrapped SSL socket.") + s.close() + else: # fake it... + print("connect:", True) + if tls: + print("wrap:", True) + print("write:", True) + + if hasSR: + # connect + recv + s = do_connect(peer_addr, tls, handshake) + # recv -> EAGAIN + try: + print("recv:", s.recv(10)) + except OSError as er: + dp(er) + print("recv:", er.args[0] == errno.EAGAIN) + s.close() + else: # fake it... + print("connect:", True) + if tls: + print("wrap:", True) + print("recv:", True) + + # connect + read + if hasRW: + s = do_connect(peer_addr, tls, handshake) + # read -> None + try: + ret = s.read(10) + print("read:", ret is None) + except OSError as er: + dp(er) + print("read:", False) # should not raise + except ValueError as er: # CPython + dp(er) + print("read:", er.args[0] == "Read on closed or unwrapped SSL socket.") + s.close() + else: # fake it... + print("connect:", True) + if tls: + print("wrap:", True) + print("read:", True) + + +if __name__ == "__main__": + # these tests use a non-existent test IP address, this way the connect takes forever and + # we can see EAGAIN/None (https://tools.ietf.org/html/rfc5737) + print("--- Plain sockets to nowhere ---") + test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False) + print("--- SSL sockets to nowhere ---") + # this test fails with AXTLS because do_handshake=False blocks on first read/write and + # there it times out until the connect is aborted + test(socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False) + print("--- Plain sockets ---") + test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, True) + print("--- SSL sockets ---") + test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True) diff --git a/tests/net_inet/ssl_errors.py b/tests/net_inet/ssl_errors.py new file mode 100644 index 000000000..fd281b1c4 --- /dev/null +++ b/tests/net_inet/ssl_errors.py @@ -0,0 +1,51 @@ +# test that socket.connect() on a non-blocking socket raises EINPROGRESS +# and that an immediate write/send/read/recv does the right thing + +import sys + +try: + import uerrno as errno, usocket as socket, ussl as ssl +except: + import errno, socket, ssl + + +def test(addr, hostname, block=True): + print("---", hostname or addr) + s = socket.socket() + s.setblocking(block) + try: + s.connect(addr) + print("connected") + except OSError as e: + if e.args[0] != errno.EINPROGRESS: + raise + print("EINPROGRESS") + + try: + if sys.implementation.name == "micropython": + s = ssl.wrap_socket(s, do_handshake=block) + else: + s = ssl.wrap_socket(s, do_handshake_on_connect=block) + print("wrap: True") + except OSError: + print("wrap: error") + + if not block: + try: + while s.write(b"0") is None: + pass + except (ValueError, OSError): # CPython raises ValueError, MicroPython raises OSError + print("write: error") + s.close() + + +if __name__ == "__main__": + # connect to plain HTTP port, oops! + addr = socket.getaddrinfo("micropython.org", 80)[0][-1] + test(addr, None) + # connect to plain HTTP port, oops! + addr = socket.getaddrinfo("micropython.org", 80)[0][-1] + test(addr, None, False) + # connect to server with self-signed cert, oops! + addr = socket.getaddrinfo("test.mosquitto.org", 8883)[0][-1] + test(addr, "test.mosquitto.org") diff --git a/tests/net_inet/test_tls_nonblock.py b/tests/net_inet/test_tls_nonblock.py new file mode 100644 index 000000000..c27ead3d5 --- /dev/null +++ b/tests/net_inet/test_tls_nonblock.py @@ -0,0 +1,116 @@ +try: + import usocket as socket, ussl as ssl, uerrno as errno, sys +except: + import socket, ssl, errno, sys, time, select + + +def test_one(site, opts): + ai = socket.getaddrinfo(site, 443) + addr = ai[0][-1] + print(addr) + + # Connect the raw socket + s = socket.socket() + s.setblocking(False) + try: + s.connect(addr) + raise OSError(-1, "connect blocks") + except OSError as e: + if e.args[0] != errno.EINPROGRESS: + raise + + if sys.implementation.name != "micropython": + # in CPython we have to wait, otherwise wrap_socket is not happy + select.select([], [s], []) + + try: + # Wrap with SSL + try: + if sys.implementation.name == "micropython": + s = ssl.wrap_socket(s, do_handshake=False) + else: + s = ssl.wrap_socket(s, do_handshake_on_connect=False) + except OSError as e: + if e.args[0] != errno.EINPROGRESS: + raise + print("wrapped") + + # CPython needs to be told to do the handshake + if sys.implementation.name != "micropython": + while True: + try: + s.do_handshake() + break + except ssl.SSLError as err: + if err.args[0] == ssl.SSL_ERROR_WANT_READ: + select.select([s], [], []) + elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: + select.select([], [s], []) + else: + raise + time.sleep(0.1) + # print("shook hands") + + # Write HTTP request + out = b"GET / HTTP/1.0\r\nHost: %s\r\n\r\n" % bytes(site, "latin") + while len(out) > 0: + n = s.write(out) + if n is None: + continue + if n > 0: + out = out[n:] + elif n == 0: + raise OSError(-1, "unexpected EOF in write") + print("wrote") + + # Read response + resp = b"" + while True: + try: + b = s.read(128) + except OSError as err: + if err.args[0] == 2: # 2=ssl.SSL_ERROR_WANT_READ: + continue + raise + if b is None: + continue + if len(b) > 0: + if len(resp) < 1024: + resp += b + elif len(b) == 0: + break + print("read") + + if resp[:7] != b"HTTP/1.": + raise ValueError("response doesn't start with HTTP/1.") + # print(resp) + + finally: + s.close() + + +SITES = [ + "google.com", + {"host": "www.google.com"}, + "micropython.org", + "pypi.org", + "api.telegram.org", + {"host": "api.pushbullet.com", "sni": True}, +] + + +def main(): + for site in SITES: + opts = {} + if isinstance(site, dict): + opts = site + site = opts["host"] + try: + test_one(site, opts) + print(site, "ok") + except Exception as e: + print(site, "error") + print("DONE") + + +main() diff --git a/tests/net_inet/test_tls_sites.py b/tests/net_inet/test_tls_sites.py index d2cb928c8..3f945efb8 100644 --- a/tests/net_inet/test_tls_sites.py +++ b/tests/net_inet/test_tls_sites.py @@ -27,6 +27,8 @@ def test_one(site, opts): s.write(b"GET / HTTP/1.0\r\nHost: %s\r\n\r\n" % bytes(site, "latin")) resp = s.read(4096) + if resp[:7] != b"HTTP/1.": + raise ValueError("response doesn't start with HTTP/1.") # print(resp) finally: @@ -36,10 +38,10 @@ def test_one(site, opts): SITES = [ "google.com", "www.google.com", + "micropython.org", + "pypi.org", "api.telegram.org", {"host": "api.pushbullet.com", "sni": True}, - # "w9rybpfril.execute-api.ap-southeast-2.amazonaws.com", - {"host": "w9rybpfril.execute-api.ap-southeast-2.amazonaws.com", "sni": True}, ] diff --git a/tests/net_inet/test_tls_sites.py.exp b/tests/net_inet/test_tls_sites.py.exp index 2f3c113d2..bc4a8dbd1 100644 --- a/tests/net_inet/test_tls_sites.py.exp +++ b/tests/net_inet/test_tls_sites.py.exp @@ -1,5 +1,6 @@ google.com ok www.google.com ok +micropython.org ok +pypi.org ok api.telegram.org ok api.pushbullet.com ok -w9rybpfril.execute-api.ap-southeast-2.amazonaws.com ok diff --git a/tests/run-multitests.py b/tests/run-multitests.py index ad032df38..cdb2730ed 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 @@ -389,7 +390,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() @@ -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() diff --git a/tests/run-tests b/tests/run-tests index a7b88ecdd..b733d8a9f 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -20,12 +20,16 @@ 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') 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 @@ -338,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') @@ -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 @@ -498,7 +501,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) diff --git a/tests/thread/stress_schedule.py b/tests/thread/stress_schedule.py index c5a402b3a..8be7f2d73 100644 --- a/tests/thread/stress_schedule.py +++ b/tests/thread/stress_schedule.py @@ -14,7 +14,11 @@ gc.disable() +_NUM_TASKS = 10000 +_TIMEOUT_MS = 10000 + n = 0 # How many times the task successfully ran. +t = None # Start time of test, assigned here to preallocate entry in globals dict. def task(x): @@ -34,9 +38,6 @@ def thread(): for i in range(8): _thread.start_new_thread(thread, ()) -_NUM_TASKS = const(10000) -_TIMEOUT_MS = const(10000) - # Wait up to 10 seconds for 10000 tasks to be scheduled. t = utime.ticks_ms() while n < _NUM_TASKS and utime.ticks_diff(utime.ticks_ms(), t) < _TIMEOUT_MS: diff --git a/tests/unix/extra_coverage.py b/tests/unix/extra_coverage.py index 36105f6ba..b4808993a 100644 --- a/tests/unix/extra_coverage.py +++ b/tests/unix/extra_coverage.py @@ -46,6 +46,19 @@ buf = uio.BufferedWriter(stream, 8) 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 7d7b7dd9f..d97de2c0e 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) @@ -144,6 +144,9 @@ OSError 0 None None +cpp None +5 +(3, 'hellocpp') frzstr1 frzstr1.py frzmpy1 diff --git a/tools/ci.sh b/tools/ci.sh new file mode 100755 index 000000000..a815e9483 --- /dev/null +++ b/tools/ci.sh @@ -0,0 +1,487 @@ +#!/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 +} + +######################################################################################## +# 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 + +function ci_code_size_setup { + sudo apt-get update + 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 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` + 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_setup { + git clone https://github.com/espressif/esp-idf.git + git -C esp-idf checkout v4.0.2 + 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 + ./esp-idf/install.sh +} + +function ci_esp32_build { + source esp-idf/export.sh + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/esp32 submodules + make ${MAKEOPTS} -C ports/esp32 +} + +######################################################################################## +# ports/esp8266 + +function ci_esp8266_setup { + 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 { + 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/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 lib/tinyusb + make ${MAKEOPTS} -C ports/rp2 +} + +######################################################################################## +# 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 + pip3 install pyhy +} + +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 DEBUG=1 + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_WB55 + make ${MAKEOPTS} -C ports/stm32/mboot BOARD=NUCLEO_WB55 + # Test mboot_pack_dfu.py created a valid file, and that its unpack-dfu command works. + BOARD_WB55=ports/stm32/boards/NUCLEO_WB55 + BUILD_WB55=ports/stm32/build-NUCLEO_WB55 + python3 ports/stm32/mboot/mboot_pack_dfu.py -k $BOARD_WB55/mboot_keys.h unpack-dfu $BUILD_WB55/firmware.pack.dfu $BUILD_WB55/firmware.unpack.dfu + diff $BUILD_WB55/firmware.unpack.dfu $BUILD_WB55/firmware.dfu +} + +######################################################################################## +# 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.13 + docker run --name zephyr-ci -d -it \ + -v "$(pwd)":/micropython \ + -e ZEPHYR_SDK_INSTALL_DIR=/opt/sdk/zephyr-sdk-0.12.2 \ + -e ZEPHYR_TOOLCHAIN_VARIANT=zephyr \ + -e ZEPHYR_BASE=/zephyrproject/zephyr \ + -w /micropython/ports/zephyr \ + zephyrprojectrtos/ci:v0.11.13 + docker ps -a +} + +function ci_zephyr_install { + docker exec zephyr-ci west init --mr v2.5.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 west build -p auto -b qemu_x86 -- -DCONF_FILE=prj_minimal.conf + docker exec zephyr-ci west build -p auto -b frdm_k64f -- -DCONF_FILE=prj_minimal.conf + docker exec zephyr-ci west build -p auto -b qemu_x86 + docker exec zephyr-ci west build -p auto -b frdm_k64f + docker exec zephyr-ci west build -p auto -b mimxrt1050_evk + docker exec zephyr-ci west build -p auto -b reel_board +} diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 9ef036826..117d1536e 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -34,13 +34,27 @@ # Public functions to be used in the manifest -def include(manifest): +def include(manifest, **kwargs): """Include another manifest. The manifest argument can be a string (filename) or an iterable of strings. Relative paths are resolved with respect to the current manifest file. + + Optional kwargs can be provided which will be available to the + included script via the `options` variable. + + e.g. include("path.py", extra_features=True) + + in path.py: + options.defaults(standard_features=True) + + # freeze minimal modules. + if options.standard_features: + # freeze standard modules. + if options.extra_features: + # freeze extra modules. """ if not isinstance(manifest, str): @@ -53,7 +67,7 @@ def include(manifest): # Applies to includes and input files. prev_cwd = os.getcwd() os.chdir(os.path.dirname(manifest)) - exec(f.read()) + exec(f.read(), globals(), {"options": IncludeOptions(**kwargs)}) os.chdir(prev_cwd) @@ -125,6 +139,18 @@ def freeze_mpy(path, script=None, opt=0): manifest_list = [] +class IncludeOptions: + def __init__(self, **kwargs): + self._kwargs = kwargs + self._defaults = {} + + def defaults(self, **kwargs): + self._defaults = kwargs + + def __getattr__(self, name): + return self._kwargs.get(name, self._defaults.get(name, None)) + + class FreezeError(Exception): pass @@ -172,6 +198,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") diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index de7cfe5d6..ea756d3ee 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 diff --git a/tools/pyboard.py b/tools/pyboard.py index c97ddbe48..069f7490d 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -253,6 +253,7 @@ def inWaiting(self): class Pyboard: def __init__(self, device, baudrate=115200, user="micro", password="python", wait=0): + self.use_raw_paste = True if device.startswith("exec:"): self.serial = ProcessToSerial(device[len("exec:") :]) elif device.startswith("execpty:"): @@ -359,6 +360,41 @@ def follow(self, timeout, data_consumer=None): # return normal and error output return data, data_err + def raw_paste_write(self, command_bytes): + # Read initial header, with window size. + data = self.serial.read(2) + window_size = data[0] | data[1] << 8 + window_remain = window_size + + # Write out the command_bytes data. + i = 0 + while i < len(command_bytes): + while window_remain == 0 or self.serial.inWaiting(): + data = self.serial.read(1) + if data == b"\x01": + # Device indicated that a new window of data can be sent. + window_remain += window_size + elif data == b"\x04": + # Device indicated abrupt end. Acknowledge it and finish. + self.serial.write(b"\x04") + return + else: + # Unexpected data from device. + raise PyboardError("unexpected read during raw paste: {}".format(data)) + # Send out as much data as possible that fits within the allowed window. + b = command_bytes[i : min(i + window_remain, len(command_bytes))] + self.serial.write(b) + window_remain -= len(b) + i += len(b) + + # Indicate end of data. + self.serial.write(b"\x04") + + # Wait for device to acknowledge end of data. + data = self.read_until(1, b"\x04") + if not data.endswith(b"\x04"): + raise PyboardError("could not complete raw paste: {}".format(data)) + def exec_raw_no_follow(self, command): if isinstance(command, bytes): command_bytes = command @@ -370,7 +406,26 @@ def exec_raw_no_follow(self, command): if not data.endswith(b">"): raise PyboardError("could not enter raw repl") - # write command + if self.use_raw_paste: + # Try to enter raw-paste mode. + self.serial.write(b"\x05A\x01") + data = self.serial.read(2) + if data == b"R\x00": + # Device understood raw-paste command but doesn't support it. + pass + elif data == b"R\x01": + # Device supports raw-paste mode, write out the command using this mode. + return self.raw_paste_write(command_bytes) + else: + # Device doesn't support raw-paste, fall back to normal raw REPL. + data = self.read_until(1, b"w REPL; CTRL-B to exit\r\n>") + if not data.endswith(b"w REPL; CTRL-B to exit\r\n>"): + print(data) + raise PyboardError("could not enter raw repl") + # Don't try to use raw-paste mode again for this connection. + self.use_raw_paste = False + + # Write command using standard raw REPL, 256 bytes every 10ms. for i in range(0, len(command_bytes), 256): self.serial.write(command_bytes[i : min(i + 256, len(command_bytes))]) time.sleep(0.01) @@ -596,7 +651,11 @@ def main(): help="Do not follow the output after running the scripts.", ) cmd_parser.add_argument( - "-f", "--filesystem", action="store_true", help="perform a filesystem action" + "-f", + "--filesystem", + action="store_true", + help="perform a filesystem action: " + "cp local :device | cp :device local | cat path | ls [path] | rm path | mkdir path | rmdir path", ) cmd_parser.add_argument("files", nargs="*", help="input files") args = cmd_parser.parse_args() diff --git a/tools/pydfu.py b/tools/pydfu.py index 030f56bf8..42b4fa2da 100755 --- a/tools/pydfu.py +++ b/tools/pydfu.py @@ -521,7 +521,7 @@ def write_elements(elements, mass_erase_used, progress=None): data = elem["data"] elem_size = size elem_addr = addr - if progress: + if progress and elem_size: progress(elem_addr, 0, elem_size) while size > 0: write_size = size diff --git a/tools/upip.py b/tools/upip.py index 0036eac25..aa8aecedf 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": diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py new file mode 100755 index 000000000..cc4b80f4a --- /dev/null +++ b/tools/verifygitlog.py @@ -0,0 +1,123 @@ +#!/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) + subject_line_format = r"^[^!]+: [A-Z]+.+ .+\.$" + if not re.match(subject_line_format, subject_line): + error("Subject line should match " + repr(subject_line_format) + ": " + 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) diff --git a/wapy-wasi b/wapy-wasi new file mode 100644 index 000000000..e69de29bb