diff --git a/.github/workflows/biome.yml b/.github/workflows/biome.yml new file mode 100644 index 000000000000..88744f16ca7d --- /dev/null +++ b/.github/workflows/biome.yml @@ -0,0 +1,16 @@ +name: JavaScript code lint and formatting with Biome + +on: [push, pull_request] + +jobs: + eslint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Biome + uses: biomejs/setup-biome@v2 + with: + version: 1.5.3 + - name: Run Biome + run: biome ci --indent-style=space --indent-width=4 tests/ ports/webassembly diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index 84d30b27f6e6..f5e01dc1f620 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -25,6 +25,7 @@ jobs: ci_func: # names are functions in ci.sh - stm32_pyb_build - stm32_nucleo_build + - stm32_misc_build runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 diff --git a/.gitmodules b/.gitmodules index 75bffdadddeb..e6712c1f9f40 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/lwip-tcpip/lwip.git [submodule "lib/berkeley-db-1.xx"] path = lib/berkeley-db-1.xx - url = https://github.com/pfalcon/berkeley-db-1.xx + url = https://github.com/micropython/berkeley-db-1.xx [submodule "lib/stm32lib"] path = lib/stm32lib url = https://github.com/micropython/stm32lib @@ -59,3 +59,12 @@ [submodule "lib/protobuf-c"] path = lib/protobuf-c url = https://github.com/protobuf-c/protobuf-c.git +[submodule "lib/open-amp"] + path = lib/open-amp + url = https://github.com/OpenAMP/open-amp.git +[submodule "lib/libmetal"] + path = lib/libmetal + url = https://github.com/OpenAMP/libmetal.git +[submodule "lib/arduino-lib"] + path = lib/arduino-lib + url = https://github.com/arduino/arduino-lib-mpy.git diff --git a/LICENSE b/LICENSE index b85f31808a48..9142c4604584 100644 --- a/LICENSE +++ b/LICENSE @@ -48,12 +48,14 @@ used during the build process and is not part of the compiled source code. /cmsis (BSD-3-clause) /crypto-algorithms (NONE) /libhydrogen (ISC) + /libmetal (BSD-3-clause) /littlefs (BSD-3-clause) /lwip (BSD-3-clause) /mynewt-nimble (Apache-2.0) /nrfx (BSD-3-clause) /nxp_driver (BSD-3-Clause) /oofatfs (BSD-1-clause) + /open-amp (BSD-3-clause) /pico-sdk (BSD-3-clause) /re15 (BSD-3-clause) /stm32lib (BSD-3-clause) diff --git a/docs/library/collections.rst b/docs/library/collections.rst index 6cf2c096ffc6..6a23e456c66a 100644 --- a/docs/library/collections.rst +++ b/docs/library/collections.rst @@ -18,7 +18,9 @@ Classes appends and pops from either side of the deque. New deques are created using the following arguments: - - *iterable* must be the empty tuple, and the new deque is created empty. + - *iterable* is an iterable used to populate the deque when it is + created. It can be an empty tuple or list to create a deque that + is initially empty. - *maxlen* must be specified and the deque will be bounded to this maximum length. Once the deque is full, any new items added will @@ -26,18 +28,37 @@ Classes - The optional *flags* can be 1 to check for overflow when adding items. - As well as supporting `bool` and `len`, deque objects have the following - methods: + Deque objects support `bool`, `len`, iteration and subscript load and store. + They also have the following methods: .. method:: deque.append(x) Add *x* to the right side of the deque. - Raises IndexError if overflow checking is enabled and there is no more room left. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the queue. + + .. method:: deque.appendleft(x) + + Add *x* to the left side of the deque. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the queue. + + .. method:: deque.pop() + + Remove and return an item from the right side of the deque. + Raises ``IndexError`` if no items are present. .. method:: deque.popleft() Remove and return an item from the left side of the deque. - Raises IndexError if no items are present. + Raises ``IndexError`` if no items are present. + + .. method:: deque.extend(iterable) + + Extend the deque by appending all the items from *iterable* to + the right of the deque. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the deque. .. function:: namedtuple(name, fields) diff --git a/docs/library/index.rst b/docs/library/index.rst index eb29b7680542..4209a0781a60 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -103,6 +103,7 @@ the following libraries. micropython.rst neopixel.rst network.rst + openamp.rst uctypes.rst vfs.rst diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index be2be2eee5ee..fcd78f1c3986 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -75,6 +75,21 @@ Methods - ``wake`` specifies the sleep mode from where this interrupt can wake up the system. +.. method:: RTC.memory([data]) + + ``RTC.memory(data)`` will write *data* to the RTC memory, where *data* is any + object which supports the buffer protocol (including `bytes`, `bytearray`, + `memoryview` and `array.array`). ``RTC.memory()`` reads RTC memory and returns + a `bytes` object. + + Data written to RTC user memory is persistent across restarts, including + `machine.soft_reset()` and `machine.deepsleep()`. + + The maximum length of RTC user memory is 2048 bytes by default on esp32, + and 492 bytes on esp8266. + + Availability: esp32, esp8266 ports. + Constants --------- diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst new file mode 100644 index 000000000000..82897b280d0f --- /dev/null +++ b/docs/library/machine.USBDevice.rst @@ -0,0 +1,286 @@ +.. currentmodule:: machine +.. _machine.USBDevice: + +class USBDevice -- USB Device driver +==================================== + +.. note:: ``machine.USBDevice`` is currently only supported on the rp2 and samd + ports. + +USBDevice provides a low-level Python API for implementing USB device functions using +Python code. This low-level API assumes familiarity with the USB standard. It's +not recommended to use this API directly, instead install the high-level usbd +module from micropython-lib. + +.. warning:: This functionality is very new and the high-level usbd module is + not yet merged into micropython-lib. It can be found `here on + GitHub `_. + +Terminology +----------- + +- A "Runtime" USB device interface or driver is one which is defined using this + Python API after MicroPython initially starts up. + +- A "Built-in" USB device interface or driver is one that is compiled into the + MicroPython firmware, and is always available. Examples are USB-CDC (serial + port) which is usually enabled by default. Built-in USB-MSC (Mass Storage) is an + option on some ports. + +Lifecycle +--------- + +Managing a runtime USB interface can be tricky, especially if you are communicating +with MicroPython over a built-in USB-CDC serial port that's part of the same USB +device. + +- A MicroPython soft reset will always clear all runtime USB interfaces, which + results in the entire USB device disconnecting from the host. If MicroPython + is also providing a built-in USB-CDC serial port then this will re-appear + after the soft reset. + + This means some functions (like ``mpremote run``) that target the USB-CDC + serial port will immediately fail if a runtime USB interface is active, + because the port goes away when ``mpremote`` triggers a soft reset. The + operation should succeed on the second try, as after the soft reset there is + no more runtime USB interface. + +- To configure a runtime USB device on every boot, it's recommended to place the + configuration code in the ``boot.py`` file on the :ref:`device VFS + `. On each reset this file is executed before the USB subsystem is + initialised (and before ``main.py``), so it allows the board to come up with the runtime + USB device immediately. + +- For development or debugging, it may be convenient to connect a hardware + serial REPL and disable the built-in USB-CDC serial port entirely. Not all ports + support this (currently only ``rp2``). The custom build should be configured + with ``#define MICROPY_HW_USB_CDC (0)`` and ``#define + MICROPY_HW_ENABLE_UART_REPL (1)``. + +Constructors +------------ + +.. class:: USBDevice() + + Construct a USBDevice object. + + .. note:: This object is a singleton, each call to this constructor + returns the same object reference. + +Methods +------- + +.. method:: USBDevice.config(desc_dev, desc_cfg, desc_strs=None, open_itf_cb=None, reset_cb=None, control_xfer_cb=None, xfer_cb=None) + + Configures the ``USBDevice`` singleton object with the USB runtime device + state and callback functions: + + - ``desc_dev`` - A bytes-like object containing + the new USB device descriptor. + + - ``desc_cfg`` - A bytes-like object containing the + new USB configuration descriptor. + + - ``desc_strs`` - Optional object holding strings or bytes objects + containing USB string descriptor values. Can be a list, a dict, or any + object which supports subscript indexing with integer keys (USB string + descriptor index). + + Strings are an optional USB feature, and this parameter can be unset + (default) if no strings are referenced in the device and configuration + descriptors, or if only built-in strings should be used. + + Apart from index 0, all the string values should be plain ASCII. Index 0 + is the special "languages" USB descriptor, represented as a bytes object + with a custom format defined in the USB standard. ``None`` can be + returned at index 0 in order to use a default "English" language + descriptor. + + To fall back to providing a built-in string value for a given index, a + subscript lookup can return ``None``, raise ``KeyError``, or raise + ``IndexError``. + + - ``open_itf_cb`` - This callback is called once for each interface + or Interface Association Descriptor in response to a Set + Configuration request from the USB Host (the final stage before + the USB device is available to the host). + + The callback takes a single argument, which is a memoryview of the + interface or IAD descriptor that the host is accepting (including + all associated descriptors). It is a view into the same + ``desc_cfg`` object that was provided as a separate + argument to this function. The memoryview is only valid until the + callback function returns. + + - ``reset_cb`` - This callback is called when the USB host performs + a bus reset. The callback takes no arguments. Any in-progress + transfers will never complete. The USB host will most likely + proceed to re-enumerate the USB device by calling the descriptor + callbacks and then ``open_itf_cb()``. + + - ``control_xfer_cb`` - This callback is called one or more times + for each USB control transfer (device Endpoint 0). It takes two + arguments. + + The first argument is the control transfer stage. It is one of: + + - ``1`` for SETUP stage. + - ``2`` for DATA stage. + - ``3`` for ACK stage. + + Second argument is a memoryview to read the USB control request + data for this stage. The memoryview is only valid until the + callback function returns. + + The callback should return one of the following values: + + - ``False`` to stall the endpoint and reject the transfer. + - ``True`` to continue the transfer to the next stage. + - A buffer object to provide data for this stage of the transfer. + This should be a writable buffer for an ``OUT`` direction transfer, or a + readable buffer with data for an ``IN`` direction transfer. + + - ``xfer_cb`` - This callback is called whenever a non-control + transfer submitted by calling :func:`USBDevice.submit_xfer` completes. + + The callback has three arguments: + + 1. The Endpoint number for the completed transfer. + 2. Result value: ``True`` if the transfer succeeded, ``False`` + otherwise. + 3. Number of bytes successfully transferred. In the case of a + "short" transfer, The result is ``True`` and ``xferred_bytes`` + will be smaller than the length of the buffer submitted for the + transfer. + + .. note:: If a bus reset occurs (see :func:`USBDevice.reset`), + ``xfer_cb`` is not called for any transfers that have not + already completed. + +.. method:: USBDevice.active(self, [value] /) + + Returns the current active state of this runtime USB device as a + boolean. The runtime USB device is "active" when it is available to + interact with the host, it doesn't mean that a USB Host is actually + present. + + If the optional ``value`` argument is set to a truthy value, then + the USB device will be activated. + + If the optional ``value`` argument is set to a falsey value, then + the USB device is deactivated. While the USB device is deactivated, + it will not be detected by the USB Host. + + To simulate a disconnect and a reconnect of the USB device, call + ``active(False)`` followed by ``active(True)``. This may be + necessary if the runtime device configuration has changed, so that + the host sees the new device. + +.. attribute:: USDBD.builtin_driver + + This attribute holds the current built-in driver configuration, and must be + set to one of the ``USBDevice.BUILTIN_`` named constants defined on this object. + + By default it holds the value :data:`USBDevice.BUILTIN_NONE`. + + Runtime USB device must be inactive when setting this field. Call the + :func:`USBDevice.active` function to deactivate before setting if necessary + (and again to activate after setting). + + If this value is set to any value other than :data:`USBDevice.BUILTIN_NONE` then + the following restrictions apply to the :func:`USBDevice.config` arguments: + + - ``desc_cfg`` should begin with the built-in USB interface descriptor data + accessible via :data:`USBDevice.builtin_driver` attribute ``desc_cfg``. + Descriptors appended after the built-in configuration descriptors should use + interface, string and endpoint numbers starting from the max built-in values + defined in :data:`USBDevice.builtin_driver` attributes ``itf_max``, ``str_max`` and + ``ep_max``. + + - The ``bNumInterfaces`` field in the built-in configuration + descriptor will also need to be updated if any new interfaces + are appended to the end of ``desc_cfg``. + + - ``desc_strs`` should either be ``None`` or a list/dictionary where index + values less than ``USBDevice.builtin_driver.str_max`` are missing or have + value ``None``. This reserves those string indexes for the built-in + drivers. Placing a different string at any of these indexes overrides that + string in the built-in driver. + +.. method:: USBDevice.submit_xfer(self, ep, buffer /) + + Submit a USB transfer on endpoint number ``ep``. ``buffer`` must be + an object implementing the buffer interface, with read access for + ``IN`` endpoints and write access for ``OUT`` endpoints. + + .. note:: ``ep`` cannot be the control Endpoint number 0. Control + transfers are built up through successive executions of + ``control_xfer_cb``, see above. + + Returns ``True`` if successful, ``False`` if the transfer could not + be queued (as USB device is not configured by host, or because + another transfer is queued on this endpoint.) + + When the USB host completes the transfer, the ``xfer_cb`` callback + is called (see above). + + Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not + active. + +.. method:: USBDevice.stall(self, ep, [stall] /) + + Calling this function gets or sets the STALL state of a device endpoint. + + ``ep`` is the number of the endpoint. + + If the optional ``stall`` parameter is set, this is a boolean flag + for the STALL state. + + The return value is the current stall state of the endpoint (before + any change made by this function). + + An endpoint that is set to STALL may remain stalled until this + function is called again, or STALL may be cleared automatically by + the USB host. + + Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not + active. + +Constants +--------- + +.. data:: USBDevice.BUILTIN_NONE +.. data:: USBDevice.BUILTIN_DEFAULT +.. data:: USBDevice.BUILTIN_CDC +.. data:: USBDevice.BUILTIN_MSC +.. data:: USBDevice.BUILTIN_CDC_MSC + + These constant objects hold the built-in descriptor data which is + compiled into the MicroPython firmware. ``USBDevice.BUILTIN_NONE`` and + ``USBDevice.BUILTIN_DEFAULT`` are always present. Additional objects may be present + depending on the firmware build configuration and the actual built-in drivers. + + .. note:: Currently at most one of ``USBDevice.BUILTIN_CDC``, + ``USBDevice.BUILTIN_MSC`` and ``USBDevice.BUILTIN_CDC_MSC`` is defined + and will be the same object as ``USBDevice.BUILTIN_DEFAULT``. + These constants are defined to allow run-time detection of + the built-in driver (if any). Support for selecting one of + multiple built-in driver configurations may be added in the + future. + + These values are assigned to :data:`USBDevice.builtin_driver` to get/set the + built-in configuration. + + Each object contains the following read-only fields: + + - ``itf_max`` - One more than the highest bInterfaceNumber value used + in the built-in configuration descriptor. + - ``ep_max`` - One more than the highest bEndpointAddress value used + in the built-in configuration descriptor. Does not include any + ``IN`` flag bit (0x80). + - ``str_max`` - One more than the highest string descriptor index + value used by any built-in descriptor. + - ``desc_dev`` - ``bytes`` object containing the built-in USB device + descriptor. + - ``desc_cfg`` - ``bytes`` object containing the complete built-in USB + configuration descriptor. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 3f5cd6f13c72..532266d1d910 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -265,3 +265,4 @@ Classes machine.WDT.rst machine.SD.rst machine.SDCard.rst + machine.USBDevice.rst diff --git a/docs/library/openamp.rst b/docs/library/openamp.rst new file mode 100644 index 000000000000..de4d51ad06b5 --- /dev/null +++ b/docs/library/openamp.rst @@ -0,0 +1,115 @@ +:mod:`openamp` -- provides standard Asymmetric Multiprocessing (AMP) support +============================================================================ + +.. module:: openamp + :synopsis: provides standard Asymmetric Multiprocessing (AMP) support + +The ``openamp`` module provides a standard inter-processor communications infrastructure +for MicroPython. The module handles all of the details of OpenAMP, such as setting up +the shared resource table, initializing vrings, etc. It provides an API for using the +RPMsg bus infrastructure with the `Endpoint` class, and provides processor Life Cycle +Management (LCM) support, such as loading firmware and starting and stopping a remote +core, via the `RemoteProc` class. + +Example usage:: + + import openamp + + def ept_recv_callback(src, data): + print("Received message on endpoint", data) + + # Create a new RPMsg endpoint to communicate with the remote core. + ept = openamp.Endpoint("vuart-channel", callback=ept_recv_callback) + + # Create a RemoteProc object, load its firmware and start it. + rproc = openamp.RemoteProc("virtual_uart.elf") # Or entry point address (ex 0x081E0000) + rproc.start() + + while True: + if ept.is_ready(): + ept.send("data") + +Functions +--------- + +.. function:: new_service_callback(ns_callback) + + Set the new service callback. + + The *ns_callback* argument is a function that will be called when the remote processor + announces new services. At that point the host processor can choose to create the + announced endpoint, if this particular service is supported, or ignore it if it's + not. If this function is not set, the host processor should first register the + endpoint locally, and it will be automatically bound when the remote announces + the service. + +Endpoint class +-------------- + +.. class:: Endpoint(name, callback, src=ENDPOINT_ADDR_ANY, dest=ENDPOINT_ADDR_ANY) + + Construct a new RPMsg Endpoint. An endpoint is a bidirectional communication + channel between two cores. + + Arguments are: + + - *name* is the name of the endpoint. + - *callback* is a function that is called when the endpoint receives data with the + source address of the remote point, and the data as bytes passed by reference. + - *src* is the endpoint source address. If none is provided one will be assigned + to the endpoint by the library. + - *dest* is the endpoint destination address. If the endpoint is created from the + new_service_callback, this must be provided and it must match the remote endpoint's + source address. If the endpoint is registered locally, before the announcement, the + destination address will be assigned by the library when the endpoint is bound. + +.. method:: Endpoint.deinit() + + Destroy the endpoint and release all of its resources. + +.. method:: Endpoint.is_ready() + + Returns True if the endpoint is ready to send (i.e., has both a source and destination addresses) + +.. method:: Endpoint.send(src=-1, dest=-1, timeout=-1) + + Send a message to the remote processor over this endpoint. + + Arguments are: + + - *src* is the source endpoint address of the message. If none is provided, the + source address the endpoint is bound to is used. + - *dest* is the destination endpoint address of the message. If none is provided, + the destination address the endpoint is bound to is used. + - *timeout* specifies the time in milliseconds to wait for a free buffer. By default + the function is blocking. + +RemoteProc class +---------------- + +.. class:: RemoteProc(entry) + + The RemoteProc object provides processor Life Cycle Management (LCM) support, such as + loading firmware, starting and stopping a remote core. + + The *entry* argument can be a path to firmware image, in which case the firmware is + loaded from file to its target memory, or an entry point address, in which case the + firmware must be loaded already at the given address. + +.. method:: RemoteProc.start() + + Starts the remote processor. + +.. method:: RemoteProc.stop() + + Stops the remote processor. The exact behavior is platform-dependent. On the STM32H7 for + example it's not possible to stop and then restart the Cortex-M4 core, so a complete + system reset is performed on a call to this function. + +.. method:: RemoteProc.shutdown() + + Shutdown stops the remote processor and releases all of its resources. The exact behavior + is platform-dependent, however typically it disables power and clocks to the remote core. + This function is also used as the finaliser (i.e., called when ``RemoteProc`` object is + collected). Note that on the STM32H7, it's not possible to stop and then restart the + Cortex-M4 core, so a complete system reset is performed on a call to this function. diff --git a/docs/library/rp2.DMA.rst b/docs/library/rp2.DMA.rst new file mode 100644 index 000000000000..c5e3f31aa2ed --- /dev/null +++ b/docs/library/rp2.DMA.rst @@ -0,0 +1,293 @@ +.. currentmodule:: rp2 +.. _rp2.DMA: + +class DMA -- access to the RP2040's DMA controller +================================================== + +The :class:`DMA` class offers access to the RP2040's Direct Memory Access (DMA) +controller, providing the ability move data between memory blocks and/or IO registers. The DMA +controller has its own, separate read and write bus master connections onto the bus fabric and +each DMA channel can independently read data from one address and write it back to another +address, optionally incrementing one or both pointers, allowing it to perform transfers on behalf +of the processor while the processor carries out other tasks or enters a low power state. The +RP2040's DMA controller has 12 independent DMA channels that can run concurrently. For full +details of the RP2040's DMA system see section 2.5 of the `RP2040 Datasheet +`_. + +Examples +-------- + +The simplest use of the DMA controller is to move data from one block of memory to another. +This can be accomplished with the following code:: + + a = bytearray(32*1024) + b = bytearray(32*1024) + d = rp2.DMA() + c = d.pack_ctrl() # Just use the default control value. + # The count is in 'transfers', which defaults to four-byte words, so divide length by 4 + d.config(read=a, write=b, count=len(a)//4, ctrl=c, trigger=True) + # Wait for completion + while d.active(): + pass + +Note that while this example sits in an idle loop while it waits for the transfer to complete, +the program could just as well do some useful work in this time instead. + +Another, perhaps more common use of the DMA controller is to transfer between memory and an IO +peripheral. In this situation the address of the IO register does not change for each transfer but +the memory address needs to be incremented. It is also necessary to control the pace of the +transfer so as to not write data before it can be accepted by a peripheral or read it before the +data is ready, and this can be controlled with the ``treq_sel`` field of the DMA channel's control +register. The various fields of the control register for each DMA channel can be packed +using the :meth:`DMA.pack_ctrl()` method and unpacked using the :meth:`DMA.unpack_ctrl()` +static method. Code to transfer data from a byte array to the TX FIFO of a PIO state machine, +one byte at a time, looks like this:: + + # pio_num is index of the PIO block being used, sm_num is the state machine in that block. + # my_state_machine is an rp2.PIO() instance. + DATA_REQUEST_INDEX = (pio_num << 3) + sm_num + + src_data = bytearray(1024) + d = rp2.DMA() + + # Transfer bytes, rather than words, don't increment the write address and pace the transfer. + c = d.pack_ctrl(size=0, inc_write=False, treq_sel=DATA_REQUEST_INDEX) + + d.config( + read=src_data, + write=my_state_machine, + count=len(src_data), + ctrl=c, + trigger=True + ) + +Note that in this example the value given for the write address is just the PIO state machine to +which we are sending the data. This works because PIO state machines present the buffer protocol, +allowing direct access to their data FIFO registers. + +Constructor +----------- + +.. class:: DMA() + + Claim one of the DMA controller channels for exclusive use. + +Methods +------- + +.. method:: DMA.config(read=None, write=None, count=None, ctrl=None, trigger=False) + + Configure the DMA registers for the channel and optionally start the transfer. + Parameters are: + + - *read*: The address from which the DMA controller will start reading data or + an object that will provide data to be read. It can be an integer or any + object that supports the buffer protocol. + - *write*: The address to which the DMA controller will start writing or an + object into which data will be written. It can be an integer or any object + that supports the buffer protocol. + - *count*: The number of bus transfers that will execute before this channel + stops. Note that this is the number of transfers, not the number of bytes. + If the transfers are 2 or 4 bytes wide then the total amount of data moved + (and thus the size of required buffer) needs to be multiplied accordingly. + - *ctrl*: The value for the DMA control register. This is an integer value + that is typically packed using the :meth:`DMA.pack_ctrl()`. + - *trigger*: Optionally commence the transfer immediately. + +.. method:: DMA.irq(handler=None, hard=False) + + Returns the IRQ object for this DMA channel and optionally configures it. + +.. method:: DMA.close() + + Release the claim on the underlying DMA channel and free the interrupt + handler. The :class:`DMA` object can not be used after this operation. + +.. method:: DMA.pack_ctrl(default=None, **kwargs) + + Pack the values provided in the keyword arguments into the named fields of a new control + register value. Any field that is not provided will be set to a default value. The + default will either be taken from the provided ``default`` value, or if that is not + given, a default suitable for the current channel; setting this to the current value + of the `DMA.ctrl` attribute provides an easy way to override a subset of the fields. + + The keys for the keyword arguments can be any key returned by the :meth:`DMA.unpack_ctrl()` + method. The writable values are: + + - *enable*: ``bool`` Set to enable the channel (default: ``True``). + + - *high_pri*: ``bool`` Make this channel's bus traffic high priority (default: ``False``). + + - *size*: ``int`` Transfer size: 0=byte, 1=half word, 2=word (default: 2). + + - *inc_read*: ``bool`` Increment the read address after each transfer (default: ``True``). + + - *inc_write*: ``bool`` Increment the write address after each transfer (default: ``True``). + + - *ring_size*: ``int`` If non-zero, only the bottom ``ring_size`` bits of one + address register will change when an address is incremented, causing the + address to wrap at the next ``1 << ring_size`` byte boundary. Which + address is wrapped is controlled by the ``ring_sel`` flag. A zero value + disables address wrapping. + + - *ring_sel*: ``bool`` Set to ``False`` to have the ``ring_size`` apply to the read address + or ``True`` to apply to the write address. + + - *chain_to*: ``int`` The channel number for a channel to trigger after this transfer + completes. Setting this value to this DMA object's own channel number + disables chaining (this is the default). + + - *treq_sel*: ``int`` Select a Transfer Request signal. See section 2.5.3 in the RP2040 + datasheet for details. + + - *irq_quiet*: ``bool`` Do not generate interrupt at the end of each transfer. Interrupts + will instead be generated when a zero value is written to the trigger + register, which will halt a sequence of chained transfers (default: + ``True``). + + - *bswap*: ``bool`` If set to true, bytes in words or half-words will be reversed before + writing (default: ``True``). + + - *sniff_en*: ``bool`` Set to ``True`` to allow data to be accessed by the chips sniff + hardware (default: ``False``). + + - *write_err*: ``bool`` Setting this to ``True`` will clear a previously reported write + error. + + - *read_err*: ``bool`` Setting this to ``True`` will clear a previously reported read + error. + + See the description of the ``CH0_CTRL_TRIG`` register in section 2.5.7 of the RP2040 + datasheet for details of all of these fields. + +.. method:: DMA.unpack_ctrl(value) + + Unpack a value for a DMA channel control register into a dictionary with key/value pairs + for each of the fields in the control register. *value* is the ``ctrl`` register value + to unpack. + + This method will return values for all the keys that can be passed to ``DMA.pack_ctrl``. + In addition, it will also return the read-only flags in the control register: ``busy``, + which goes high when a transfer starts and low when it ends, and ``ahb_err``, which is + the logical OR of the ``read_err`` and ``write_err`` flags. These values will be ignored + when packing, so that the dictionary created by unpacking a control register can be used + directly as the keyword arguments for packing. + +.. method:: DMA.active([value]) + + Gets or sets whether the DMA channel is currently running. + + >>> sm.active() + 0 + >>> sm.active(1) + >>> while sm.active(): + ... pass + +Attributes +---------- + +.. attribute:: DMA.read + + This attribute reflects the address from which the next bus transfer + will read. It may be written with either an integer or an object + that supports the buffer protocol and doing so has immediate effect. + +.. attribute:: DMA.write + + This attribute reflects the address to which the next bus transfer + will write. It may be written with either an integer or an object + that supports the buffer protocol and doing so has immediate effect. + +.. attribute:: DMA.count + + Reading this attribute will return the number of remaining bus + transfers in the *current* transfer sequence. Writing this attribute + sets the total number of transfers to be the *next* transfer sequence. + +.. attribute:: DMA.ctrl + + This attribute reflects DMA channel control register. It is typically written + with an integer packed using the :meth:`DMA.pack_ctrl()` method. The returned + register value can be unpacked using the :meth:`DMA.unpack_ctrl()` method. + +.. attribute:: DMA.channel + + The channel number of the DMA channel. This can be passed in the ``chain_to`` + argument of `DMA.pack_ctrl()` on another channel to allow DMA chaining. + +.. attribute:: DMA.registers + + This attribute is an array-like object that allows direct access to + the DMA channel's registers. The index is by word, rather than by byte, + so the register indices are the register address offsets divided by 4. + See the RP2040 data sheet for register details. + +Chaining and trigger register access +------------------------------------ + +The DMA controller in the RP2040 offers a couple advanced features to allow one DMA channel +to initiate a transfer on another channel. One is the use of the ``chain_to`` value in the +control register and the other is writing to one of the DMA channel's registers that has a +trigger effect. When coupled with the ability to have one DMA channel write directly to the +`DMA.registers` of another channel, this allows for complex transactions to be performed +without any CPU intervention. + +Below is an example of using both chaining and register +triggering to implement gathering of multiple blocks of data into a single destination. Full +details of these features can be found in section 2.5 of the RP2040 data sheet and the code +below is a Pythonic version of the example in sub-section 2.5.6.2. + +.. code-block:: python + + from rp2 import DMA + from uctypes import addressof + from array import array + + def gather_strings(string_list, buf): + # We use two DMA channels. The first sends lengths and source addresses from the gather + # list to the registers of the second. The second copies the data itself. + gather_dma = DMA() + buffer_dma = DMA() + + # Pack up length/address pairs to be sent to the registers. + gather_list = array("I") + + for s in string_list: + gather_list.append(len(s)) + gather_list.append(addressof(s)) + + gather_list.append(0) + gather_list.append(0) + + # When writing to the registers of the second DMA channel, we need to wrap the + # write address on an 8-byte (1<<3 bytes) boundary. We write to the ``TRANS_COUNT`` + # and ``READ_ADD_TRIG`` registers in the last register alias (registers 14 and 15). + gather_ctrl = gather_dma.pack_ctrl(ring_size=3, ring_sel=True) + gather_dma.config( + read=gather_list, write=buffer_dma.registers[14:16], + count=2, ctrl=gather_ctrl + ) + + # When copying the data, the transfer size is single bytes, and when completed we need + # to chain back to the start another gather DMA transaction. + buffer_ctrl = buffer_dma.pack_ctrl(size=0, chain_to=gather_dma.channel) + # The read and count values will be set by the other DMA channel. + buffer_dma.config(write=buf, ctrl=buffer_ctrl) + + # Set the transfer in motion. + gather_dma.active(1) + + # Wait until all the register values have been sent + end_address = addressof(gather_list) + 4 * len(gather_list) + while gather_dma.read != end_address: + pass + + input = ["This is ", "a ", "test", " of the scatter", " gather", " process"] + output = bytearray(64) + + print(output) + gather_strings(input, output) + print(output) + +This example idles while waiting for the transfer to complete; alternatively it could +set an interrupt handler and return immediately. diff --git a/docs/library/rp2.StateMachine.rst b/docs/library/rp2.StateMachine.rst index e8c167c09f57..1cb87e90b6e2 100644 --- a/docs/library/rp2.StateMachine.rst +++ b/docs/library/rp2.StateMachine.rst @@ -140,3 +140,10 @@ Methods Optionally configure it. +Buffer protocol +--------------- + +The StateMachine class supports the `buffer protocol`, allowing direct access to the transmit +and receive FIFOs for each state machine. This is primarily in order to allow StateMachine +objects to be passed directly as the read or write parameters when configuring a `rp2.DMA()` +channel. diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst index 7a473387b4a8..f0189327dfb7 100644 --- a/docs/library/rp2.rst +++ b/docs/library/rp2.rst @@ -241,6 +241,7 @@ Classes .. toctree:: :maxdepth: 1 + rp2.DMA.rst rp2.Flash.rst rp2.PIO.rst rp2.StateMachine.rst diff --git a/docs/library/struct.rst b/docs/library/struct.rst index 026cb5e8ac3b..c2357505960d 100644 --- a/docs/library/struct.rst +++ b/docs/library/struct.rst @@ -45,6 +45,8 @@ The following data types are supported: +--------+--------------------+-------------------+---------------+ | Q | unsigned long long | integer (`1`) | 8 | +--------+--------------------+-------------------+---------------+ +| e | n/a (half-float) | float (`2`) | 2 | ++--------+--------------------+-------------------+---------------+ | f | float | float (`2`) | 4 | +--------+--------------------+-------------------+---------------+ | d | double | float (`2`) | 8 | diff --git a/docs/reference/mpyfiles.rst b/docs/reference/mpyfiles.rst index 02bac3d411e0..8e8284928dac 100644 --- a/docs/reference/mpyfiles.rst +++ b/docs/reference/mpyfiles.rst @@ -86,7 +86,8 @@ and .mpy version. =================== ============ MicroPython release .mpy version =================== ============ -v1.22.0 and up 6.2 +v1.23.0 and up 6.3 +v1.22.x 6.2 v1.20 - v1.21.0 6.1 v1.19.x 6 v1.12 - v1.18 5 @@ -102,6 +103,7 @@ MicroPython repository at which the .mpy version was changed. =================== ======================================== .mpy version change Git commit =================== ======================================== +6.2 to 6.3 bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b 6.1 to 6.2 6967ff3c581a66f73e9f3d78975f47528db39980 6 to 6.1 d94141e1473aebae0d3c63aeaa8397651ad6fa01 5 to 6 f2040bfc7ee033e48acef9f289790f3b4e6b74e5 diff --git a/drivers/memory/external_flash_device.h b/drivers/memory/external_flash_device.h index 03798446036a..b5cf4a8877ff 100644 --- a/drivers/memory/external_flash_device.h +++ b/drivers/memory/external_flash_device.h @@ -442,6 +442,23 @@ typedef struct { .single_status_byte = false, \ } +// Settings for the ISSI devices +#define IS25LPWP064D { \ + .total_size = (1 << 23), /* 8 MiB */ \ + .start_up_time_us = 5000, \ + .manufacturer_id = 0x9D, \ + .memory_type = 0x60, \ + .capacity = 0x17, \ + .max_clock_speed_mhz = 80, \ + .quad_enable_bit_mask = 0x40, \ + .has_sector_protection = false, \ + .supports_fast_read = true, \ + .supports_qspi = true, \ + .supports_qspi_writes = true, \ + .write_status_register_split = false, \ + .single_status_byte = true, \ +} + // Settings for a GENERIC device with the most common setting #define GENERIC { \ .total_size = (1 << 21), /* 2 MiB */ \ diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index d795102b4aad..218ec15a2a00 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -11,9 +11,10 @@ SRC = btree_c.c btree_py.py ARCH = x64 BTREE_DIR = $(MPY_DIR)/lib/berkeley-db-1.xx -BTREE_DEFS = -D__DBINTERFACE_PRIVATE=1 -Dmpool_error="(void)" -Dabort=abort_ "-Dvirt_fd_t=void*" $(BTREE_DEFS_EXTRA) -CFLAGS += -I$(BTREE_DIR)/PORT/include -CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter $(BTREE_DEFS) +BERKELEY_DB_CONFIG_FILE ?= \"extmod/berkeley-db/berkeley_db_config_port.h\" +CFLAGS += -I$(BTREE_DIR)/include +CFLAGS += -DBERKELEY_DB_CONFIG_FILE=$(BERKELEY_DB_CONFIG_FILE) +CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter SRC += $(addprefix $(realpath $(BTREE_DIR))/,\ btree/bt_close.c \ diff --git a/examples/natmod/btree/btree_c.c b/examples/natmod/btree/btree_c.c index 3c60e41b2d66..bbf51c731fbd 100644 --- a/examples/natmod/btree/btree_c.c +++ b/examples/natmod/btree/btree_c.c @@ -39,6 +39,10 @@ void abort_(void) { nlr_raise(mp_obj_new_exception(mp_load_global(MP_QSTR_RuntimeError))); } +int puts(const char *s) { + return mp_printf(&mp_plat_print, "%s\n", s); +} + int native_errno; #if defined(__linux__) int *__errno_location (void) diff --git a/examples/natmod/framebuf/framebuf.c b/examples/natmod/framebuf/framebuf.c index e8f99b3b20cd..1ba702e33d90 100644 --- a/examples/natmod/framebuf/framebuf.c +++ b/examples/natmod/framebuf/framebuf.c @@ -1,3 +1,4 @@ +#define MICROPY_PY_ARRAY (1) #define MICROPY_PY_FRAMEBUF (1) #include "py/dynruntime.h" @@ -12,7 +13,7 @@ mp_obj_full_type_t mp_type_framebuf; #include "extmod/modframebuf.c" -mp_map_elem_t framebuf_locals_dict_table[11]; +mp_map_elem_t framebuf_locals_dict_table[12]; static MP_DEFINE_CONST_DICT(framebuf_locals_dict, framebuf_locals_dict_table); mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { @@ -30,9 +31,10 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a framebuf_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_rect), MP_OBJ_FROM_PTR(&framebuf_rect_obj) }; framebuf_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_line), MP_OBJ_FROM_PTR(&framebuf_line_obj) }; framebuf_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_ellipse), MP_OBJ_FROM_PTR(&framebuf_ellipse_obj) }; - framebuf_locals_dict_table[8] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) }; - framebuf_locals_dict_table[9] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) }; - framebuf_locals_dict_table[10] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) }; + framebuf_locals_dict_table[8] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_poly), MP_OBJ_FROM_PTR(&framebuf_poly_obj) }; + framebuf_locals_dict_table[9] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) }; + framebuf_locals_dict_table[10] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) }; + framebuf_locals_dict_table[11] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) }; MP_OBJ_TYPE_SET_SLOT(&mp_type_framebuf, locals_dict, (void*)&framebuf_locals_dict, 2); mp_store_global(MP_QSTR_FrameBuffer, MP_OBJ_FROM_PTR(&mp_type_framebuf)); diff --git a/extmod/berkeley-db/berkeley_db_config_port.h b/extmod/berkeley-db/berkeley_db_config_port.h new file mode 100644 index 000000000000..41e4acd81e80 --- /dev/null +++ b/extmod/berkeley-db/berkeley_db_config_port.h @@ -0,0 +1,16 @@ +// Berkeley-db configuration. + +#define __DBINTERFACE_PRIVATE 1 +#define mpool_error printf +#define abort abort_ +#define virt_fd_t void* + +#ifdef MICROPY_BERKELEY_DB_DEFPSIZE +#define DEFPSIZE MICROPY_BERKELEY_DB_DEFPSIZE +#endif + +#ifdef MICROPY_BERKELEY_DB_MINCACHE +#define MINCACHE MICROPY_BERKELEY_DB_MINCACHE +#endif + +__attribute__((noreturn)) void abort_(void); diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 53c774013236..60f1a23ad6b2 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -18,6 +18,7 @@ set(MICROPY_SOURCE_EXTMOD ${MICROPY_EXTMOD_DIR}/machine_signal.c ${MICROPY_EXTMOD_DIR}/machine_spi.c ${MICROPY_EXTMOD_DIR}/machine_uart.c + ${MICROPY_EXTMOD_DIR}/machine_usb_device.c ${MICROPY_EXTMOD_DIR}/machine_wdt.c ${MICROPY_EXTMOD_DIR}/modbluetooth.c ${MICROPY_EXTMOD_DIR}/modframebuf.c @@ -131,27 +132,27 @@ if(MICROPY_PY_BTREE) ) target_include_directories(micropy_extmod_btree PRIVATE - ${MICROPY_LIB_BERKELEY_DIR}/PORT/include + ${MICROPY_LIB_BERKELEY_DIR}/include ) + if(NOT BERKELEY_DB_CONFIG_FILE) + set(BERKELEY_DB_CONFIG_FILE "${MICROPY_DIR}/extmod/berkeley-db/berkeley_db_config_port.h") + endif() + target_compile_definitions(micropy_extmod_btree PRIVATE - __DBINTERFACE_PRIVATE=1 - mpool_error=printf - abort=abort_ - "virt_fd_t=void*" + BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" ) # The include directories and compile definitions below are needed to build # modbtree.c and should be added to the main MicroPython target. list(APPEND MICROPY_INC_CORE - "${MICROPY_LIB_BERKELEY_DIR}/PORT/include" + "${MICROPY_LIB_BERKELEY_DIR}/include" ) list(APPEND MICROPY_DEF_CORE MICROPY_PY_BTREE=1 - __DBINTERFACE_PRIVATE=1 - "virt_fd_t=void*" + BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" ) list(APPEND MICROPY_SOURCE_EXTMOD diff --git a/extmod/extmod.mk b/extmod/extmod.mk index f0428bcd0565..f7c6f9988e67 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -15,6 +15,7 @@ SRC_EXTMOD_C += \ extmod/machine_spi.c \ extmod/machine_timer.c \ extmod/machine_uart.c \ + extmod/machine_usb_device.c \ extmod/machine_wdt.c \ extmod/modasyncio.c \ extmod/modbinascii.c \ @@ -30,6 +31,9 @@ SRC_EXTMOD_C += \ extmod/modmachine.c \ extmod/modnetwork.c \ extmod/modonewire.c \ + extmod/modopenamp.c \ + extmod/modopenamp_remoteproc.c \ + extmod/modopenamp_remoteproc_store.c \ extmod/modos.c \ extmod/modplatform.c\ extmod/modrandom.c \ @@ -377,8 +381,10 @@ endif ifeq ($(MICROPY_PY_BTREE),1) BTREE_DIR = lib/berkeley-db-1.xx -BTREE_DEFS = -D__DBINTERFACE_PRIVATE=1 -Dmpool_error=printf -Dabort=abort_ "-Dvirt_fd_t=void*" $(BTREE_DEFS_EXTRA) -INC += -I$(TOP)/$(BTREE_DIR)/PORT/include +BERKELEY_DB_CONFIG_FILE ?= \"extmod/berkeley-db/berkeley_db_config_port.h\" +CFLAGS_EXTMOD += -DBERKELEY_DB_CONFIG_FILE=$(BERKELEY_DB_CONFIG_FILE) +CFLAGS_EXTMOD += $(BTREE_DEFS_EXTRA) +INC += -I$(TOP)/$(BTREE_DIR)/include SRC_THIRDPARTY_C += $(addprefix $(BTREE_DIR)/,\ btree/bt_close.c \ btree/bt_conv.c \ @@ -397,9 +403,7 @@ SRC_THIRDPARTY_C += $(addprefix $(BTREE_DIR)/,\ ) CFLAGS_EXTMOD += -DMICROPY_PY_BTREE=1 # we need to suppress certain warnings to get berkeley-db to compile cleanly -# and we have separate BTREE_DEFS so the definitions don't interfere with other source code -$(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter -Wno-deprecated-non-prototype -Wno-unknown-warning-option $(BTREE_DEFS) -$(BUILD)/extmod/modbtree.o: CFLAGS += $(BTREE_DEFS) +$(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter -Wno-deprecated-non-prototype -Wno-unknown-warning-option endif ################################################################################ @@ -514,3 +518,58 @@ include $(TOP)/extmod/btstack/btstack.mk endif endif + +################################################################################ +# openamp + +ifeq ($(MICROPY_PY_OPENAMP),1) +OPENAMP_DIR = lib/open-amp +LIBMETAL_DIR = lib/libmetal +GIT_SUBMODULES += $(LIBMETAL_DIR) $(OPENAMP_DIR) +include $(TOP)/extmod/libmetal/libmetal.mk + +INC += -I$(TOP)/$(OPENAMP_DIR) +CFLAGS += -DMICROPY_PY_OPENAMP=1 + +ifeq ($(MICROPY_PY_OPENAMP_REMOTEPROC),1) +CFLAGS += -DMICROPY_PY_OPENAMP_REMOTEPROC=1 +endif + +CFLAGS_THIRDPARTY += \ + -I$(BUILD)/openamp \ + -I$(TOP)/$(OPENAMP_DIR) \ + -I$(TOP)/$(OPENAMP_DIR)/lib/include/ \ + -DMETAL_INTERNAL \ + -DVIRTIO_DRIVER_ONLY \ + -DNO_ATOMIC_64_SUPPORT \ + -DRPMSG_BUFFER_SIZE=512 \ + +# OpenAMP's source files. +SRC_OPENAMP_C += $(addprefix $(OPENAMP_DIR)/lib/,\ + rpmsg/rpmsg.c \ + rpmsg/rpmsg_virtio.c \ + virtio/virtio.c \ + virtio/virtqueue.c \ + virtio_mmio/virtio_mmio_drv.c \ + ) + +# OpenAMP's remoteproc source files. +ifeq ($(MICROPY_PY_OPENAMP_REMOTEPROC),1) +SRC_OPENAMP_C += $(addprefix $(OPENAMP_DIR)/lib/remoteproc/,\ + elf_loader.c \ + remoteproc.c \ + remoteproc_virtio.c \ + rsc_table_parser.c \ + ) +endif # MICROPY_PY_OPENAMP_REMOTEPROC + +# Disable compiler warnings in OpenAMP (variables used only for assert). +$(BUILD)/$(OPENAMP_DIR)/lib/rpmsg/rpmsg_virtio.o: CFLAGS += -Wno-unused-but-set-variable +$(BUILD)/$(OPENAMP_DIR)/lib/virtio_mmio/virtio_mmio_drv.o: CFLAGS += -Wno-unused-but-set-variable + +# We need to have generated libmetal before compiling OpenAMP. +$(addprefix $(BUILD)/, $(SRC_OPENAMP_C:.c=.o)): $(BUILD)/openamp/metal/config.h + +SRC_THIRDPARTY_C += $(SRC_LIBMETAL_C) $(SRC_OPENAMP_C) + +endif # MICROPY_PY_OPENAMP diff --git a/extmod/libmetal/libmetal.mk b/extmod/libmetal/libmetal.mk new file mode 100644 index 000000000000..852b04289833 --- /dev/null +++ b/extmod/libmetal/libmetal.mk @@ -0,0 +1,51 @@ +# Makefile directives for libmetal + +# libmetal is intended to run through a pre-processor (as part of its CMake-based build system). +# This replicates the basic functionality of the pre-processor, including adding a "micropython" +# platform that is almost identical to the built-in "generic" platform with a few small changes +# provided by the files in extmod/libmetal. +$(BUILD)/openamp: $(BUILD) + $(MKDIR) -p $@ + +$(BUILD)/openamp/metal: $(BUILD)/openamp + $(MKDIR) -p $@ + +$(BUILD)/openamp/metal/config.h: $(BUILD)/openamp/metal $(TOP)/$(LIBMETAL_DIR)/lib/config.h + @$(ECHO) "GEN $@" + @for file in $(TOP)/$(LIBMETAL_DIR)/lib/*.c $(TOP)/$(LIBMETAL_DIR)/lib/*.h; do $(SED) -e "s/@PROJECT_SYSTEM@/micropython/g" -e "s/@PROJECT_PROCESSOR@/arm/g" $$file > $(BUILD)/openamp/metal/$$(basename $$file); done + $(MKDIR) -p $(BUILD)/openamp/metal/processor/arm + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/processor/arm/*.h $(BUILD)/openamp/metal/processor/arm + $(MKDIR) -p $(BUILD)/openamp/metal/compiler/gcc + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/compiler/gcc/*.h $(BUILD)/openamp/metal/compiler/gcc + $(MKDIR) -p $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/system/generic/*.c $(TOP)/$(LIBMETAL_DIR)/lib/system/generic/*.h $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/extmod/libmetal/metal/system/micropython/* $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/extmod/libmetal/metal/config.h $(BUILD)/openamp/metal/config.h + +# libmetal's source files. +SRC_LIBMETAL_C := $(addprefix $(BUILD)/openamp/metal/,\ + device.c \ + dma.c \ + init.c \ + io.c \ + irq.c \ + log.c \ + shmem.c \ + softirq.c \ + version.c \ + device.c \ + system/micropython/condition.c \ + system/micropython/device.c \ + system/micropython/io.c \ + system/micropython/irq.c \ + system/micropython/shmem.c \ + system/micropython/time.c \ + ) + +# These files are generated by the rule above (along with config.h), so we need to make the .c +# files depend on that step -- can't use the .o files as the target because the .c files don't +# exist yet. +$(SRC_LIBMETAL_C): $(BUILD)/openamp/metal/config.h + +# Qstr processing will include the generated libmetal headers, so add them as a qstr requirement. +QSTR_GLOBAL_REQUIREMENTS += $(BUILD)/openamp/metal/config.h diff --git a/extmod/libmetal/metal/config.h b/extmod/libmetal/metal/config.h new file mode 100644 index 000000000000..459a2f4ca424 --- /dev/null +++ b/extmod/libmetal/metal/config.h @@ -0,0 +1,81 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * MicroPython libmetal config. + */ +#ifndef MICROPY_INCLUDED_METAL_MICROPYTHON_H +#define MICROPY_INCLUDED_METAL_MICROPYTHON_H + +#include + +// Port-specific config. +#include "mpmetalport.h" + +// Library major version number. +#define METAL_VER_MAJOR 1 + +// Library minor version number. +#define METAL_VER_MINOR 5 + +// Library patch level. +#define METAL_VER_PATCH 0 + +// Library version string. +#define METAL_VER "1.5.0" + +#if METAL_HAVE_STDATOMIC_H +#define HAVE_STDATOMIC_H +#endif + +#if METAL_HAVE_FUTEX_H +#define HAVE_FUTEX_H +#endif + +#ifndef METAL_MAX_DEVICE_REGIONS +#define METAL_MAX_DEVICE_REGIONS 1 +#endif + +// generic/log.h +#if METAL_LOG_HANDLER_ENABLE +#include "py/mphal.h" +#undef metal_log +#define metal_log(level, ...) mp_printf(&mp_plat_print, __VA_ARGS__) +#endif + +static inline void *__metal_allocate_memory(unsigned int size) { + return m_tracked_calloc(1, size); +} + +static inline void __metal_free_memory(void *ptr) { + m_tracked_free(ptr); +} + +// The following functions must be provided by the port (in mpmetalport.h / mpmetalport.c). +int __metal_sleep_usec(unsigned int usec); +void sys_irq_enable(unsigned int vector); +void sys_irq_disable(unsigned int vector); +void sys_irq_restore_enable(unsigned int flags); +unsigned int sys_irq_save_disable(void); +#endif // MICROPY_INCLUDED_METAL_MICROPYTHON_H diff --git a/extmod/libmetal/metal/system/micropython/alloc.h b/extmod/libmetal/metal/system/micropython/alloc.h new file mode 100644 index 000000000000..dd0fdb13f925 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/alloc.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/log.h b/extmod/libmetal/metal/system/micropython/log.h new file mode 100644 index 000000000000..dd0fdb13f925 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/log.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/sleep.h b/extmod/libmetal/metal/system/micropython/sleep.h new file mode 100644 index 000000000000..dd0fdb13f925 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/sleep.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/sys.h b/extmod/libmetal/metal/system/micropython/sys.h new file mode 100644 index 000000000000..0f404dcd6128 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/sys.h @@ -0,0 +1,5 @@ +#include + +struct metal_state { + struct metal_common_state common; +}; diff --git a/extmod/machine_mem.c b/extmod/machine_mem.c index c8f0889b9a72..c34ece2454ca 100644 --- a/extmod/machine_mem.c +++ b/extmod/machine_mem.c @@ -27,7 +27,7 @@ #include "py/runtime.h" #include "extmod/modmachine.h" -#if MICROPY_PY_MACHINE +#if MICROPY_PY_MACHINE_MEMX // If you wish to override the functions for mapping the machine_mem read/write // address, then add a #define for MICROPY_MACHINE_MEM_GET_READ_ADDR and/or @@ -113,4 +113,4 @@ const machine_mem_obj_t machine_mem8_obj = {{&machine_mem_type}, 1}; const machine_mem_obj_t machine_mem16_obj = {{&machine_mem_type}, 2}; const machine_mem_obj_t machine_mem32_obj = {{&machine_mem_type}, 4}; -#endif // MICROPY_PY_MACHINE +#endif // MICROPY_PY_MACHINE_MEMX diff --git a/extmod/machine_signal.c b/extmod/machine_signal.c index 6295a496b365..d2e7bc58cac0 100644 --- a/extmod/machine_signal.c +++ b/extmod/machine_signal.c @@ -24,12 +24,11 @@ * THE SOFTWARE. */ -#include "py/mpconfig.h" -#if MICROPY_PY_MACHINE - #include - #include "py/runtime.h" + +#if MICROPY_PY_MACHINE_SIGNAL + #include "extmod/modmachine.h" #include "extmod/virtpin.h" @@ -181,4 +180,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &signal_locals_dict ); -#endif // MICROPY_PY_MACHINE +#endif // MICROPY_PY_MACHINE_SIGNAL diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c new file mode 100644 index 000000000000..69c3f3848700 --- /dev/null +++ b/extmod/machine_usb_device.c @@ -0,0 +1,335 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Angus Gratton + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mpconfig.h" + +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +#include "mp_usbd.h" +#include "py/mperrno.h" +#include "py/objstr.h" + +// Implements the singleton runtime USB object +// +// Currently this implementation references TinyUSB directly. + +#ifndef NO_QSTR +#include "device/usbd_pvt.h" +#endif + +#define HAS_BUILTIN_DRIVERS (MICROPY_HW_USB_CDC || MICROPY_HW_USB_MSC) + +const mp_obj_type_t machine_usb_device_type; + +static mp_obj_t usb_device_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type; + (void)n_args; + (void)n_kw; + (void)args; + + if (MP_STATE_VM(usbd) == MP_OBJ_NULL) { + mp_obj_usb_device_t *o = m_new0(mp_obj_usb_device_t, 1); + o->base.type = &machine_usb_device_type; + o->desc_dev = mp_const_none; + o->desc_cfg = mp_const_none; + o->desc_strs = mp_const_none; + o->open_itf_cb = mp_const_none; + o->reset_cb = mp_const_none; + o->control_xfer_cb = mp_const_none; + o->xfer_cb = mp_const_none; + for (int i = 0; i < CFG_TUD_ENDPPOINT_MAX; i++) { + o->xfer_data[i][0] = mp_const_none; + o->xfer_data[i][1] = mp_const_none; + } + o->builtin_driver = MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none); + o->active = false; // Builtin USB may be active already, but runtime is inactive + o->trigger = false; + o->control_data = MP_OBJ_TO_PTR(mp_obj_new_memoryview('B', 0, NULL)); + o->num_pend_excs = 0; + for (int i = 0; i < MP_USBD_MAX_PEND_EXCS; i++) { + o->pend_excs[i] = mp_const_none; + } + + MP_STATE_VM(usbd) = MP_OBJ_FROM_PTR(o); + } + + return MP_STATE_VM(usbd); +} + +// Utility helper to raise an error if USB device is not active +// (or if a change of active state is triggered but not processed.) +static void usb_device_check_active(mp_obj_usb_device_t *usbd) { + if (!usbd->active || usbd->trigger) { + mp_raise_OSError(MP_EINVAL); + } +} + +static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buffer) { + mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(self); + int ep_addr; + mp_buffer_info_t buf_info = { 0 }; + bool result; + + usb_device_check_active(usbd); + + // Unmarshal arguments, raises TypeError if invalid + ep_addr = mp_obj_get_int(ep); + mp_get_buffer_raise(buffer, &buf_info, ep_addr & TUSB_DIR_IN_MASK ? MP_BUFFER_READ : MP_BUFFER_RW); + + uint8_t ep_num = tu_edpt_number(ep_addr); + uint8_t ep_dir = tu_edpt_dir(ep_addr); + + if (ep_num == 0 || ep_num >= CFG_TUD_ENDPPOINT_MAX) { + // TinyUSB usbd API doesn't range check arguments, so this check avoids + // out of bounds array access, or submitting transfers on the control endpoint. + // + // This C layer doesn't otherwise keep track of which endpoints the host + // is aware of (or not). + mp_raise_ValueError("ep"); + } + + if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) { + mp_raise_OSError(MP_EBUSY); + } + + result = usbd_edpt_xfer(USBD_RHPORT, ep_addr, buf_info.buf, buf_info.len); + + if (result) { + // Store the buffer object until the transfer completes + usbd->xfer_data[ep_num][ep_dir] = buffer; + } + + return mp_obj_new_bool(result); +} +static MP_DEFINE_CONST_FUN_OBJ_3(usb_device_submit_xfer_obj, usb_device_submit_xfer); + +static mp_obj_t usb_device_active(size_t n_args, const mp_obj_t *args) { + mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]); + + bool result = usbd->active; + if (n_args == 2) { + bool value = mp_obj_is_true(args[1]); + + if (value != result) { + if (value + && !mp_usb_device_builtin_enabled(usbd) + && usbd->desc_dev == mp_const_none) { + // Only allow activating if config() has already been called to set some descriptors, or a + // built-in driver is enabled + mp_raise_OSError(MP_EINVAL); + } + + // Any change to active state is triggered here and processed + // from the TinyUSB task. + usbd->active = value; + usbd->trigger = true; + if (value) { + mp_usbd_init(); // Ensure TinyUSB has initialised by this point + } + mp_usbd_schedule_task(); + } + } + + return mp_obj_new_bool(result); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_active_obj, 1, 2, usb_device_active); + +static mp_obj_t usb_device_stall(size_t n_args, const mp_obj_t *args) { + mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]); + int epnum = mp_obj_get_int(args[1]); + + usb_device_check_active(self); + + mp_obj_t res = mp_obj_new_bool(usbd_edpt_stalled(USBD_RHPORT, epnum)); + + if (n_args == 3) { // Set stall state + mp_obj_t stall = args[2]; + if (mp_obj_is_true(stall)) { + usbd_edpt_stall(USBD_RHPORT, epnum); + } else { + usbd_edpt_clear_stall(USBD_RHPORT, epnum); + } + } + + return res; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_stall_obj, 2, 3, usb_device_stall); + +// Configure the singleton USB device with all of the relevant transfer and descriptor +// callbacks for dynamic devices. +static mp_obj_t usb_device_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(pos_args[0]); + + enum { ARG_desc_dev, ARG_desc_cfg, ARG_desc_strs, ARG_open_itf_cb, + ARG_reset_cb, ARG_control_xfer_cb, ARG_xfer_cb, ARG_active }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_desc_dev, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_desc_cfg, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_desc_strs, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_open_itf_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_reset_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_control_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Check descriptor arguments + mp_obj_t desc_dev = args[ARG_desc_dev].u_obj; + mp_obj_t desc_cfg = args[ARG_desc_cfg].u_obj; + mp_obj_t desc_strs = args[ARG_desc_strs].u_obj; + if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_dev), buffer)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_dev")); + } + if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_cfg), buffer)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_cfg")); + } + if (desc_strs != mp_const_none + && !MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_strs), subscr)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_strs")); + } + + self->desc_dev = desc_dev; + self->desc_cfg = desc_cfg; + self->desc_strs = desc_strs; + self->open_itf_cb = args[ARG_open_itf_cb].u_obj; + self->reset_cb = args[ARG_reset_cb].u_obj; + self->control_xfer_cb = args[ARG_control_xfer_cb].u_obj; + self->xfer_cb = args[ARG_xfer_cb].u_obj; + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(usb_device_config_obj, 1, usb_device_config); + +static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_dev_obj, + &mp_usbd_builtin_desc_dev, sizeof(tusb_desc_device_t)); + +#if HAS_BUILTIN_DRIVERS +// BUILTIN_DEFAULT Python object holds properties of the built-in USB configuration +// (i.e. values used by the C implementation of TinyUSB devices.) +static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_cfg_obj, + mp_usbd_builtin_desc_cfg, MP_USBD_BUILTIN_DESC_CFG_LEN); + +static const mp_rom_map_elem_t usb_device_builtin_default_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(USBD_ITF_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(USBD_EP_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(USBD_STR_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) }, + { MP_ROM_QSTR(MP_QSTR_desc_cfg), MP_ROM_PTR(&builtin_default_desc_cfg_obj) }, +}; +static MP_DEFINE_CONST_DICT(usb_device_builtin_default_dict, usb_device_builtin_default_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_usb_device_builtin_default, + MP_QSTR_BUILTIN_DEFAULT, + MP_TYPE_FLAG_NONE, + locals_dict, &usb_device_builtin_default_dict + ); +#endif // HAS_BUILTIN_DRIVERS + +// BUILTIN_NONE holds properties for no enabled built-in USB device support +static const mp_rom_map_elem_t usb_device_builtin_none_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) }, + { MP_ROM_QSTR(MP_QSTR_desc_cfg), mp_const_empty_bytes }, +}; +static MP_DEFINE_CONST_DICT(usb_device_builtin_none_dict, usb_device_builtin_none_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_usb_device_builtin_none, + MP_QSTR_BUILTIN_NONE, + MP_TYPE_FLAG_NONE, + locals_dict, &usb_device_builtin_none_dict + ); + +static const mp_rom_map_elem_t usb_device_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&usb_device_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_submit_xfer), MP_ROM_PTR(&usb_device_submit_xfer_obj) }, + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&usb_device_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_stall), MP_ROM_PTR(&usb_device_stall_obj) }, + + // Built-in driver constants + { MP_ROM_QSTR(MP_QSTR_BUILTIN_NONE), MP_ROM_PTR(&mp_type_usb_device_builtin_none) }, + + #if !HAS_BUILTIN_DRIVERS + // No builtin-in drivers, so BUILTIN_DEFAULT is BUILTIN_NONE + { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_none) }, + #else + { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + + // Specific driver constant names are to support future switching of built-in drivers, + // but currently only one is present and it maps directly to BUILTIN_DEFAULT + #if MICROPY_HW_USB_CDC && !MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #if MICROPY_HW_USB_MSC && !MICROPY_HW_USB_CDC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #if MICROPY_HW_USB_CDC && MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #endif // !HAS_BUILTIN_DRIVERS +}; +static MP_DEFINE_CONST_DICT(usb_device_locals_dict, usb_device_locals_dict_table); + +static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_obj_usb_device_t *self = MP_OBJ_TO_PTR(self_in); + if (dest[0] == MP_OBJ_NULL) { + // Load attribute. + if (attr == MP_QSTR_builtin_driver) { + dest[0] = self->builtin_driver; + } else { + // Continue lookup in locals_dict. + dest[1] = MP_OBJ_SENTINEL; + } + } else if (dest[1] != MP_OBJ_NULL) { + // Store attribute. + if (attr == MP_QSTR_builtin_driver) { + if (self->active) { + mp_raise_OSError(MP_EINVAL); // Need to deactivate first + } + // Note: this value should be one of the BUILTIN_nnn constants, + // but not checked here to save code size in a low level API + self->builtin_driver = dest[1]; + dest[0] = MP_OBJ_NULL; + } + } +} + +MP_DEFINE_CONST_OBJ_TYPE( + machine_usb_device_type, + MP_QSTR_USBDevice, + MP_TYPE_FLAG_NONE, + make_new, usb_device_make_new, + locals_dict, &usb_device_locals_dict, + attr, &usb_device_attr + ); + +MP_REGISTER_ROOT_POINTER(mp_obj_t usbd); + +#endif diff --git a/extmod/modbtree.c b/extmod/modbtree.c index f8c38e0ad430..55c13ac911a7 100644 --- a/extmod/modbtree.c +++ b/extmod/modbtree.c @@ -57,8 +57,8 @@ #undef CIRCLEQ_INSERT_TAIL #undef CIRCLEQ_REMOVE -#include -#include <../../btree/btree.h> +#include "berkeley-db/db.h" +#include "berkeley-db/btree.h" typedef struct _mp_obj_btree_t { mp_obj_base_t base; diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index 693e837f786a..f3ce5b5ef0ec 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -577,9 +577,7 @@ static mp_obj_t framebuf_ellipse(size_t n_args, const mp_obj_t *args_in) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf_ellipse_obj, 6, 8, framebuf_ellipse); -#if MICROPY_PY_ARRAY && !MICROPY_ENABLE_DYNRUNTIME -// TODO: poly needs mp_binary_get_size & mp_binary_get_val_array which aren't -// available in dynruntime.h yet. +#if MICROPY_PY_ARRAY static mp_int_t poly_int(mp_buffer_info_t *bufinfo, size_t index) { return mp_obj_get_int(mp_binary_get_val_array(bufinfo->typecode, bufinfo->buf, index)); @@ -696,7 +694,8 @@ static mp_obj_t framebuf_poly(size_t n_args, const mp_obj_t *args_in) { return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf_poly_obj, 5, 6, framebuf_poly); -#endif // MICROPY_PY_ARRAY && !MICROPY_ENABLE_DYNRUNTIME + +#endif // MICROPY_PY_ARRAY static mp_obj_t framebuf_blit(size_t n_args, const mp_obj_t *args_in) { mp_obj_framebuf_t *self = MP_OBJ_TO_PTR(args_in[0]); diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 607143bb7e4c..d4e25917a67b 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -38,6 +38,7 @@ #if MICROPY_PY_LWIP #include "shared/netutils/netutils.h" +#include "modnetwork.h" #include "lwip/init.h" #include "lwip/tcp.h" @@ -353,8 +354,12 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { } } +#if LWIP_VERSION_MAJOR < 2 +#define IPADDR_STRLEN_MAX 46 +#endif mp_obj_t lwip_format_inet_addr(const ip_addr_t *ip, mp_uint_t port) { - char *ipstr = ipaddr_ntoa(ip); + char ipstr[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(ip, ipstr, sizeof(ipstr)); mp_obj_t tuple[2] = { tuple[0] = mp_obj_new_str(ipstr, strlen(ipstr)), tuple[1] = mp_obj_new_int(port), @@ -1750,7 +1755,11 @@ static mp_obj_t lwip_getaddrinfo(size_t n_args, const mp_obj_t *args) { state.status = 0; MICROPY_PY_LWIP_ENTER + #if LWIP_VERSION_MAJOR < 2 err_t ret = dns_gethostbyname(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state); + #else + err_t ret = dns_gethostbyname_addrtype(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state, mp_mod_network_prefer_dns_use_ip_version == 4 ? LWIP_DNS_ADDRTYPE_IPV4_IPV6 : LWIP_DNS_ADDRTYPE_IPV6_IPV4); + #endif MICROPY_PY_LWIP_EXIT switch (ret) { diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 90e2a38a73cf..2a7e315bbb0c 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -43,10 +43,13 @@ static void mp_machine_idle(void); NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args); #endif -#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS -static mp_obj_t mp_machine_unique_id(void); +#if MICROPY_PY_MACHINE_RESET NORETURN static void mp_machine_reset(void); static mp_int_t mp_machine_reset_cause(void); +#endif + +#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS +static mp_obj_t mp_machine_unique_id(void); static mp_obj_t mp_machine_get_freq(void); static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args); static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args); @@ -77,12 +80,7 @@ static mp_obj_t machine_idle(void) { } static MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); -#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS - -static mp_obj_t machine_unique_id(void) { - return mp_machine_unique_id(); -} -MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); +#if MICROPY_PY_MACHINE_RESET NORETURN static mp_obj_t machine_reset(void) { mp_machine_reset(); @@ -94,6 +92,15 @@ static mp_obj_t machine_reset_cause(void) { } MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); +#endif + +#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS + +static mp_obj_t machine_unique_id(void) { + return mp_machine_unique_id(); +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); + static mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { return mp_machine_get_freq(); @@ -138,9 +145,11 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_machine) }, // Memory access objects. + #if MICROPY_PY_MACHINE_MEMX { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + #endif // Miscellaneous functions. #if MICROPY_PY_MACHINE_BARE_METAL_FUNCS @@ -152,7 +161,7 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_BOOTLOADER { MP_ROM_QSTR(MP_QSTR_bootloader), MP_ROM_PTR(&machine_bootloader_obj) }, #endif - #if MICROPY_PY_MACHINE_BARE_METAL_FUNCS + #if MICROPY_PY_MACHINE_RESET { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, #endif @@ -186,7 +195,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_PIN_BASE { MP_ROM_QSTR(MP_QSTR_PinBase), MP_ROM_PTR(&machine_pinbase_type) }, #endif + #if MICROPY_PY_MACHINE_SIGNAL { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, + #endif // Classes for software bus protocols. #if MICROPY_PY_MACHINE_SOFTI2C @@ -221,6 +232,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_UART { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, #endif + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + { MP_ROM_QSTR(MP_QSTR_USBDevice), MP_ROM_PTR(&machine_usb_device_type) }, + #endif #if MICROPY_PY_MACHINE_WDT { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) }, #endif diff --git a/extmod/modmachine.h b/extmod/modmachine.h index e6b08b3fc965..7c16ed302ee2 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -213,6 +213,7 @@ extern const mp_obj_type_t machine_signal_type; extern const mp_obj_type_t machine_spi_type; extern const mp_obj_type_t machine_timer_type; extern const mp_obj_type_t machine_uart_type; +extern const mp_obj_type_t machine_usbd_type; extern const mp_obj_type_t machine_wdt_type; #if MICROPY_PY_MACHINE_SOFTI2C @@ -230,6 +231,10 @@ extern const mp_machine_spi_p_t mp_machine_soft_spi_p; extern const mp_obj_dict_t mp_machine_spi_locals_dict; #endif +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +extern const mp_obj_type_t machine_usb_device_type; +#endif + #if defined(MICROPY_MACHINE_MEM_GET_READ_ADDR) uintptr_t MICROPY_MACHINE_MEM_GET_READ_ADDR(mp_obj_t addr_o, uint align); #endif diff --git a/extmod/modnetwork.c b/extmod/modnetwork.c index c8d2b9e3ff40..aa237bd93c15 100644 --- a/extmod/modnetwork.c +++ b/extmod/modnetwork.c @@ -141,10 +141,17 @@ mp_obj_t mod_network_hostname(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_network_hostname_obj, 0, 1, mod_network_hostname); +#if LWIP_VERSION_MAJOR >= 2 +MP_DEFINE_CONST_FUN_OBJ_KW(mod_network_ipconfig_obj, 0, mod_network_ipconfig); +#endif + static const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, { MP_ROM_QSTR(MP_QSTR_country), MP_ROM_PTR(&mod_network_country_obj) }, { MP_ROM_QSTR(MP_QSTR_hostname), MP_ROM_PTR(&mod_network_hostname_obj) }, + #if LWIP_VERSION_MAJOR >= 2 + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&mod_network_ipconfig_obj) }, + #endif // Defined per port in mpconfigport.h #ifdef MICROPY_PORT_NETWORK_INTERFACES diff --git a/extmod/modnetwork.h b/extmod/modnetwork.h index 0a7897faaa90..2ff9ce09dea1 100644 --- a/extmod/modnetwork.h +++ b/extmod/modnetwork.h @@ -69,10 +69,18 @@ extern char mod_network_hostname_data[MICROPY_PY_NETWORK_HOSTNAME_MAX_LEN + 1]; mp_obj_t mod_network_hostname(size_t n_args, const mp_obj_t *args); #if MICROPY_PY_LWIP + +#include "lwip/init.h" + struct netif; void mod_network_lwip_init(void); void mod_network_lwip_poll_wrapper(uint32_t ticks_ms); mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args); +#if LWIP_VERSION_MAJOR >= 2 +mp_obj_t mod_network_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); +mp_obj_t mod_network_nic_ipconfig(struct netif *netif, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); +extern int mp_mod_network_prefer_dns_use_ip_version; +#endif #elif defined(MICROPY_PORT_NETWORK_INTERFACES) struct _mod_network_socket_obj_t; diff --git a/extmod/modopenamp.c b/extmod/modopenamp.c new file mode 100644 index 000000000000..eb19c4b737ac --- /dev/null +++ b/extmod/modopenamp.c @@ -0,0 +1,402 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * OpenAMP's Python module. + */ + +#if MICROPY_PY_OPENAMP + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" +#include "metal/device.h" +#include "metal/utilities.h" + +#include "openamp/open_amp.h" +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" +#include "modopenamp.h" + +#if !MICROPY_ENABLE_FINALISER +#error "MICROPY_PY_OPENAMP requires MICROPY_ENABLE_FINALISER" +#endif + +#if MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +#define VIRTIO_DEV_ID 0xFF +#define VIRTIO_DEV_FEATURES (1 << VIRTIO_RPMSG_F_NS) + +#define VRING0_ID 0 // VRING0 ID (host to remote) fixed to 0 for linux compatibility +#define VRING1_ID 1 // VRING1 ID (remote to host) fixed to 1 for linux compatibility +#define VRING_NOTIFY_ID VRING0_ID + +#define VRING_COUNT 2 +#define VRING_ALIGNMENT 32 +// Note the number of buffers must be a power of 2 +#define VRING_NUM_BUFFS 64 + +// The following config should be enough for about 128 descriptors. +// See lib/include/openamp/virtio_ring.h for the layout of vrings +// and vring_size() to calculate the vring size. +#define VRING_RX_ADDR (METAL_SHM_ADDR) +#define VRING_TX_ADDR (METAL_SHM_ADDR + 0x1000) +#define VRING_BUFF_ADDR (METAL_SHM_ADDR + 0x2000) +#define VRING_BUFF_SIZE (METAL_SHM_SIZE - 0x2000) + +static const char openamp_trace_buf[128]; +#define MICROPY_PY_OPENAMP_TRACE_BUF ((uint32_t)openamp_trace_buf) +#define MICROPY_PY_OPENAMP_TRACE_BUF_LEN sizeof(MICROPY_PY_OPENAMP_TRACE_BUF) + +#endif // MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE + +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +#if MICROPY_PY_OPENAMP_REMOTEPROC +extern mp_obj_type_t openamp_remoteproc_type; +#endif + +static struct metal_device shm_device = { + .name = METAL_SHM_NAME, + // The number of IO regions is fixed and must match the number and + // layout of the remote processor's IO regions. The first region is + // used for the vring shared memory, and the second one is used for + // the shared resource table. + .num_regions = METAL_MAX_DEVICE_REGIONS, + .regions = { { 0 } }, + .node = { NULL }, + .irq_num = 0, + .irq_info = NULL +}; +static metal_phys_addr_t shm_physmap[] = { 0 }; + +// ###################### Virtio device class ###################### +typedef struct _virtio_dev_obj_t { + mp_obj_base_t base; + struct rpmsg_virtio_device rvdev; + struct rpmsg_virtio_shm_pool shm_pool; + mp_obj_t ns_callback; +} virtio_dev_obj_t; + +static mp_obj_t virtio_dev_deinit(mp_obj_t self_in) { + virtio_dev_obj_t *virtio_device = MP_OBJ_TO_PTR(self_in); + rpmsg_deinit_vdev(&virtio_device->rvdev); + metal_finish(); + MP_STATE_PORT(virtio_device) = NULL; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(virtio_dev_deinit_obj, virtio_dev_deinit); + +static const mp_rom_map_elem_t virtio_dev_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_VirtIODev) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&virtio_dev_deinit_obj) }, +}; +static MP_DEFINE_CONST_DICT(virtio_dev_locals_dict, virtio_dev_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + virtio_dev_type, + MP_QSTR_VirtIODev, + MP_TYPE_FLAG_NONE, + locals_dict, &virtio_dev_locals_dict + ); + +// ###################### RPMsg Endpoint class ###################### +typedef struct _endpoint_obj_t { + mp_obj_base_t base; + mp_obj_t name; + mp_obj_t callback; + struct rpmsg_endpoint ep; +} endpoint_obj_t; + +static const mp_obj_type_t endpoint_type; + +static int endpoint_recv_callback(struct rpmsg_endpoint *ept, void *data, size_t len, uint32_t src, void *priv) { + debug_printf("endpoint_recv_callback() message received src: %lu msg len: %d\n", src, len); + endpoint_obj_t *self = metal_container_of(ept, endpoint_obj_t, ep); + if (self->callback != mp_const_none) { + mp_call_function_2(self->callback, mp_obj_new_int(src), mp_obj_new_bytearray_by_ref(len, data)); + } + return 0; +} + +static mp_obj_t endpoint_send(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_src, ARG_dest, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_src, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_dest, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_timeout, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + endpoint_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + if (is_rpmsg_ept_ready(&self->ep) == false) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Endpoint not ready")); + } + + uint32_t src = self->ep.addr; + if (args[ARG_src].u_int != -1) { + src = args[ARG_src].u_int; + } + + uint32_t dest = self->ep.dest_addr; + if (args[ARG_dest].u_int != -1) { + dest = args[ARG_dest].u_int; + } + + mp_buffer_info_t rbuf; + mp_get_buffer_raise(pos_args[1], &rbuf, MP_BUFFER_READ); + debug_printf("endpoint_send() msg len: %d\n", rbuf.len); + + int bytes = 0; + mp_int_t timeout = args[ARG_timeout].u_int; + for (mp_uint_t start = mp_hal_ticks_ms(); ;) { + bytes = rpmsg_send_offchannel_raw(&self->ep, src, dest, rbuf.buf, rbuf.len, false); + if (bytes > 0 || timeout == 0) { + MICROPY_EVENT_POLL_HOOK + break; + } + if (timeout > 0 && (mp_hal_ticks_ms() - start > timeout)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("timeout waiting for a free buffer")); + } + MICROPY_EVENT_POLL_HOOK + } + return mp_obj_new_int(bytes); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(endpoint_send_obj, 2, endpoint_send); + +static mp_obj_t endpoint_is_ready(mp_obj_t self_in) { + endpoint_obj_t *self = MP_OBJ_TO_PTR(self_in); + return is_rpmsg_ept_ready(&self->ep) ? mp_const_true : mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_1(endpoint_is_ready_obj, endpoint_is_ready); + +static mp_obj_t endpoint_deinit(mp_obj_t self_in) { + endpoint_obj_t *self = MP_OBJ_TO_PTR(self_in); + rpmsg_destroy_ept(&self->ep); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(endpoint_deinit_obj, endpoint_deinit); + +static mp_obj_t endpoint_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_name, ARG_callback, ARG_src, ARG_dest }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_name, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_callback, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_src, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = RPMSG_ADDR_ANY } }, + { MP_QSTR_dest, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = RPMSG_ADDR_ANY } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + endpoint_obj_t *self = mp_obj_malloc_with_finaliser(endpoint_obj_t, &endpoint_type); + self->name = args[ARG_name].u_obj; + self->callback = args[ARG_callback].u_obj; + + if (MP_STATE_PORT(virtio_device) == NULL) { + openamp_init(); + } + + if (rpmsg_create_ept(&self->ep, &MP_STATE_PORT(virtio_device)->rvdev.rdev, mp_obj_str_get_str(self->name), + args[ARG_src].u_int, args[ARG_dest].u_int, endpoint_recv_callback, NULL) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to create RPMsg endpoint")); + } + + return MP_OBJ_FROM_PTR(self); +} + +static const mp_rom_map_elem_t endpoint_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_Endpoint) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&endpoint_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&endpoint_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_ready), MP_ROM_PTR(&endpoint_is_ready_obj) }, +}; +static MP_DEFINE_CONST_DICT(endpoint_locals_dict, endpoint_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + endpoint_type, + MP_QSTR_Endpoint, + MP_TYPE_FLAG_NONE, + make_new, endpoint_make_new, + locals_dict, &endpoint_locals_dict + ); + +// ###################### openamp module ###################### +void openamp_remoteproc_notified(mp_sched_node_t *node) { + (void)node; + rproc_virtio_notified(MP_STATE_PORT(virtio_device)->rvdev.vdev, VRING_NOTIFY_ID); +} + +static void openamp_ns_callback(struct rpmsg_device *rdev, const char *name, uint32_t dest) { + debug_printf("rpmsg_new_service_callback() new service request name: %s dest %lu\n", name, dest); + // The remote processor advertises its presence to the host by sending + // the Name Service (NS) announcement containing the name of the channel. + virtio_dev_obj_t *virtio_device = metal_container_of(rdev, virtio_dev_obj_t, rvdev); + if (virtio_device->ns_callback != mp_const_none) { + mp_call_function_2(virtio_device->ns_callback, mp_obj_new_int(dest), mp_obj_new_str(name, strlen(name))); + } +} + +#if MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +// The shared resource table must be initialized manually by the host here, +// because it's not located in the data region, so the startup code doesn't +// know about it. +static void openamp_rsc_table_init(openamp_rsc_table_t **rsc_table_out) { + openamp_rsc_table_t *rsc_table = METAL_RSC_ADDR; + memset(rsc_table, 0, METAL_RSC_SIZE); + + rsc_table->version = 1; + rsc_table->num = MP_ARRAY_SIZE(rsc_table->offset); + rsc_table->offset[0] = offsetof(openamp_rsc_table_t, vdev); + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + rsc_table->offset[1] = offsetof(openamp_rsc_table_t, trace); + #endif + rsc_table->vdev = (struct fw_rsc_vdev) { + RSC_VDEV, VIRTIO_ID_RPMSG, 0, VIRTIO_DEV_FEATURES, 0, 0, 0, VRING_COUNT, {0, 0} + }; + rsc_table->vring0 = (struct fw_rsc_vdev_vring) { + VRING_TX_ADDR, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING0_ID, 0 + }; + rsc_table->vring1 = (struct fw_rsc_vdev_vring) { + VRING_RX_ADDR, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING1_ID, 0 + }; + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + rsc_table->trace = (struct fw_rsc_trace) { + RSC_TRACE, MICROPY_PY_OPENAMP_TRACE_BUF, MICROPY_PY_OPENAMP_TRACE_BUF_LEN, 0, "trace_buf" + }; + #endif + #ifdef VIRTIO_USE_DCACHE + // Flush resource table. + metal_cache_flush((uint32_t *)rsc_table, sizeof(openamp_rsc_table_t)); + #endif + *rsc_table_out = rsc_table; +} +#endif // MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE + +static mp_obj_t openamp_new_service_callback(mp_obj_t ns_callback) { + if (MP_STATE_PORT(virtio_device) == NULL) { + openamp_init(); + } + if (ns_callback != mp_const_none && !mp_obj_is_callable(ns_callback)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid callback")); + } + MP_STATE_PORT(virtio_device)->ns_callback = ns_callback; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_new_service_callback_obj, openamp_new_service_callback); + +void openamp_init(void) { + if (MP_STATE_PORT(virtio_device) != NULL) { + // Already initialized. + return; + } + + struct metal_device *device; + struct metal_init_params metal_params = METAL_INIT_DEFAULTS; + + // Initialize libmetal. + metal_init(&metal_params); + + // Initialize the shared resource table. + openamp_rsc_table_t *rsc_table; + openamp_rsc_table_init(&rsc_table); + + if (metal_register_generic_device(&shm_device) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to register metal device")); + } + + if (metal_device_open("generic", METAL_SHM_NAME, &device) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to open metal device")); + } + + // Initialize shared memory IO region. + metal_io_init(&device->regions[0], (void *)METAL_SHM_ADDR, (void *)shm_physmap, METAL_SHM_SIZE, -1U, 0, NULL); + struct metal_io_region *shm_io = metal_device_io_region(device, 0); + if (shm_io == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to initialize device io region")); + } + + // Initialize resource table IO region. + metal_io_init(&device->regions[1], (void *)rsc_table, (void *)rsc_table, sizeof(*rsc_table), -1U, 0, NULL); + struct metal_io_region *rsc_io = metal_device_io_region(device, 1); + if (rsc_io == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to initialize device io region")); + } + + // Create virtio device. + struct virtio_device *vdev = rproc_virtio_create_vdev(RPMSG_HOST, VIRTIO_DEV_ID, + &rsc_table->vdev, rsc_io, NULL, metal_rproc_notify, NULL); + if (vdev == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to create virtio device")); + } + + // Initialize vrings. + struct fw_rsc_vdev_vring *vring_rsc = &rsc_table->vring0; + for (int i = 0; i < VRING_COUNT; i++, vring_rsc++) { + if (rproc_virtio_init_vring(vdev, vring_rsc->notifyid, vring_rsc->notifyid, + (void *)vring_rsc->da, shm_io, vring_rsc->num, vring_rsc->align) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to initialize vrings")); + } + } + + virtio_dev_obj_t *virtio_device = mp_obj_malloc_with_finaliser(virtio_dev_obj_t, &virtio_dev_type); + virtio_device->ns_callback = mp_const_none; + + // The remote processor detects that the virtio device is ready by polling + // the status field in the resource table. + rpmsg_virtio_init_shm_pool(&virtio_device->shm_pool, (void *)VRING_BUFF_ADDR, (size_t)VRING_BUFF_SIZE); + rpmsg_init_vdev(&virtio_device->rvdev, vdev, openamp_ns_callback, shm_io, &virtio_device->shm_pool); + + MP_STATE_PORT(virtio_device) = virtio_device; +} + +static const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_openamp) }, + { MP_ROM_QSTR(MP_QSTR_ENDPOINT_ADDR_ANY), MP_ROM_INT(RPMSG_ADDR_ANY) }, + { MP_ROM_QSTR(MP_QSTR_new_service_callback), MP_ROM_PTR(&openamp_new_service_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_Endpoint), MP_ROM_PTR(&endpoint_type) }, + #if MICROPY_PY_OPENAMP_REMOTEPROC + { MP_ROM_QSTR(MP_QSTR_RemoteProc), MP_ROM_PTR(&openamp_remoteproc_type) }, + #endif +}; +static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t openamp_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t)&globals_dict, +}; + +MP_REGISTER_ROOT_POINTER(struct _virtio_dev_obj_t *virtio_device); +MP_REGISTER_MODULE(MP_QSTR_openamp, openamp_module); + +#endif // MICROPY_PY_OPENAMP diff --git a/extmod/modopenamp.h b/extmod/modopenamp.h new file mode 100644 index 000000000000..8f677788f90b --- /dev/null +++ b/extmod/modopenamp.h @@ -0,0 +1,93 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * OpenAMP's Python module. + */ +#ifndef MICROPY_INCLUDED_MODOPENAMP_H +#define MICROPY_INCLUDED_MODOPENAMP_H + +// Include a port config file if one is defined. +#ifdef MICROPY_PY_OPENAMP_CONFIG_FILE +#include MICROPY_PY_OPENAMP_CONFIG_FILE +#endif + +// Use the default resource table layout and initialization code. +// Note ports and boards can override the default table and provide +// a custom one in openamp_rsc_table_t and openamp_rsc_table_init() +#ifndef MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +#define MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE (1) +#endif + +// This enables a trace buffer that can be used by remote processor for +// writing trace logs. This is enabled by default to increase the default +// resource table's compatibility with common OpenAMP examples. +#ifndef MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE +#define MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE (1) +#endif + +// For ports that don't define a custom image store, this enables a generic +// VFS-based image store that supports loading elf files from storage. +#ifndef MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE +#define MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE (1) +#endif + +// Enable or disable support for loading elf files. This option saves +// around 7KBs when disabled. +#ifndef MICROPY_PY_OPENAMP_REMOTEPROC_ELFLD_ENABLE +#define MICROPY_PY_OPENAMP_REMOTEPROC_ELFLD_ENABLE (1) +#endif + +// The resource table is used for sharing the configuration of the virtio +// device, vrings and other resources, between the host and remote cores. +// The layout and address the table structure must match the one used in +// the remote processor's firmware. +#if MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +typedef struct openamp_rsc_table { + unsigned int version; + unsigned int num; + unsigned int reserved[2]; + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + unsigned int offset[2]; + #else + unsigned int offset[1]; + #endif + struct fw_rsc_vdev vdev; + struct fw_rsc_vdev_vring vring0; + struct fw_rsc_vdev_vring vring1; + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + struct fw_rsc_trace trace; + #endif +} openamp_rsc_table_t; +#endif // MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE + +// Performs low-level initialization of OpenAMP, such as initializing libmetal, +// the shared resource table, shared memory regions, vrings and virtio device. +void openamp_init(void); + +// Ports should run this callback in scheduler context, when +// the remote processor notifies the host of pending messages. +void openamp_remoteproc_notified(mp_sched_node_t *node); + +#endif // MICROPY_INCLUDED_MODOPENAMP_H diff --git a/extmod/modopenamp_remoteproc.c b/extmod/modopenamp_remoteproc.c new file mode 100644 index 000000000000..6f43c71546bf --- /dev/null +++ b/extmod/modopenamp_remoteproc.c @@ -0,0 +1,175 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * OpenAMP's remoteproc class. + */ + +#if MICROPY_PY_OPENAMP_REMOTEPROC + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" + +#include "openamp/open_amp.h" +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +#include "modopenamp.h" +#include "modopenamp_remoteproc.h" + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +#if !MICROPY_PY_OPENAMP +#error "MICROPY_PY_OPENAMP_REMOTEPROC requires MICROPY_PY_OPENAMP" +#endif + +typedef struct openamp_remoteproc_obj { + mp_obj_base_t base; + struct remoteproc rproc; +} openamp_remoteproc_obj_t; + +const mp_obj_type_t openamp_remoteproc_type; + +// Port-defined image store operations. +extern struct image_store_ops openamp_remoteproc_store_ops; + +// Port-defined remote-proc operations. +const struct remoteproc_ops openamp_remoteproc_ops = { + .init = mp_openamp_remoteproc_init, + .mmap = mp_openamp_remoteproc_mmap, + .start = mp_openamp_remoteproc_start, + .stop = mp_openamp_remoteproc_stop, + .config = mp_openamp_remoteproc_config, + .remove = mp_openamp_remoteproc_remove, + .shutdown = mp_openamp_remoteproc_shutdown, +}; + +static mp_obj_t openamp_remoteproc_start(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Start the processor to run the application. + int error = remoteproc_start(&self->rproc); + if (error != 0) { + self->rproc.state = RPROC_ERROR; + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_start_obj, openamp_remoteproc_start); + +static mp_obj_t openamp_remoteproc_stop(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Stop the processor, but the processor is not powered down. + int error = remoteproc_stop(&self->rproc); + if (error != 0) { + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_stop_obj, openamp_remoteproc_stop); + +static mp_obj_t openamp_remoteproc_shutdown(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Shutdown the remoteproc and release its resources. + int error = remoteproc_shutdown(&self->rproc); + if (error != 0) { + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_shutdown_obj, openamp_remoteproc_shutdown); + +mp_obj_t openamp_remoteproc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_entry }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_entry, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + openamp_remoteproc_obj_t *self = mp_obj_malloc_with_finaliser(openamp_remoteproc_obj_t, &openamp_remoteproc_type); + + // Make sure OpenAMP is initialized. + if (MP_STATE_PORT(virtio_device) == NULL) { + openamp_init(); + } + + // Create a remoteproc instance. + // NOTE: ports should use rproc->priv to allocate the image store, + // which gets passed to remoteproc_load(), and all of the store ops. + remoteproc_init(&self->rproc, &openamp_remoteproc_ops, NULL); + + // Configure the remote before loading applications (optional). + int error = remoteproc_config(&self->rproc, NULL); + if (error != 0) { + mp_raise_OSError(error); + } + + if (mp_obj_is_int(args[ARG_entry].u_obj)) { + self->rproc.bootaddr = mp_obj_get_int(args[ARG_entry].u_obj); + } else { + #if MICROPY_PY_OPENAMP_REMOTEPROC_ELFLD_ENABLE + // Load firmware. + const char *path = mp_obj_str_get_str(args[ARG_entry].u_obj); + int error = remoteproc_load(&self->rproc, path, self->rproc.priv, &openamp_remoteproc_store_ops, NULL); + if (error != 0) { + mp_raise_OSError(error); + } + #else + mp_raise_TypeError(MP_ERROR_TEXT("loading firmware is not supported.")); + #endif + } + return MP_OBJ_FROM_PTR(self); +} + +static const mp_rom_map_elem_t openamp_remoteproc_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_RemoteProc) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&openamp_remoteproc_shutdown_obj) }, + { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&openamp_remoteproc_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&openamp_remoteproc_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_shutdown), MP_ROM_PTR(&openamp_remoteproc_shutdown_obj) }, +}; +static MP_DEFINE_CONST_DICT(openamp_remoteproc_dict, openamp_remoteproc_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + openamp_remoteproc_type, + MP_QSTR_RemoteProc, + MP_TYPE_FLAG_NONE, + make_new, openamp_remoteproc_make_new, + locals_dict, &openamp_remoteproc_dict + ); + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC diff --git a/extmod/modopenamp_remoteproc.h b/extmod/modopenamp_remoteproc.h new file mode 100644 index 000000000000..9bc2b07064b4 --- /dev/null +++ b/extmod/modopenamp_remoteproc.h @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * OpenAMP's remoteproc class. + */ +#ifndef MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H +#define MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H + +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +void *mp_openamp_remoteproc_store_alloc(void); +struct remoteproc *mp_openamp_remoteproc_init(struct remoteproc *rproc, + const struct remoteproc_ops *ops, void *arg); +void *mp_openamp_remoteproc_mmap(struct remoteproc *rproc, metal_phys_addr_t *pa, + metal_phys_addr_t *da, size_t size, unsigned int attribute, + struct metal_io_region **io); +int mp_openamp_remoteproc_start(struct remoteproc *rproc); +int mp_openamp_remoteproc_stop(struct remoteproc *rproc); +int mp_openamp_remoteproc_config(struct remoteproc *rproc, void *data); +void mp_openamp_remoteproc_remove(struct remoteproc *rproc); +int mp_openamp_remoteproc_shutdown(struct remoteproc *rproc); + +#endif // MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H diff --git a/extmod/modopenamp_remoteproc_store.c b/extmod/modopenamp_remoteproc_store.c new file mode 100644 index 000000000000..857c133469d6 --- /dev/null +++ b/extmod/modopenamp_remoteproc_store.c @@ -0,0 +1,146 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * OpenAMP's remoteproc store. + */ + +#if MICROPY_PY_OPENAMP_REMOTEPROC + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" + +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +#include "modopenamp.h" +#include "modopenamp_remoteproc.h" + +#if MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +// Note the initial file buffer size needs to be at least 512 to read +// enough of the elf headers on the first call to store_open(), and on +// subsequent calls to store functions, it gets reallocated if needed. +#define RPROC_FILE_STORE_BUF_SIZE (1024) + +typedef struct openamp_remoteproc_filestore { + size_t len; + uint8_t *buf; + mp_obj_t file; +} openamp_remoteproc_filestore_t; + +void *mp_openamp_remoteproc_store_alloc(void) { + // Allocate an rproc filestore. + openamp_remoteproc_filestore_t *fstore; + fstore = metal_allocate_memory(sizeof(openamp_remoteproc_filestore_t)); + fstore->len = RPROC_FILE_STORE_BUF_SIZE; + fstore->buf = metal_allocate_memory(RPROC_FILE_STORE_BUF_SIZE); + return fstore; +} + +static int openamp_remoteproc_store_open(void *store, const char *path, const void **image_data) { + DEBUG_printf("store_open(): %s\n", path); + mp_obj_t args[2] = { + mp_obj_new_str(path, strlen(path)), + MP_OBJ_NEW_QSTR(MP_QSTR_rb), + }; + + openamp_remoteproc_filestore_t *fstore = store; + fstore->file = mp_vfs_open(MP_ARRAY_SIZE(args), args, (mp_map_t *)&mp_const_empty_map); + + int error = 0; + mp_uint_t bytes = mp_stream_read_exactly(fstore->file, fstore->buf, RPROC_FILE_STORE_BUF_SIZE, &error); + if (error != 0 || bytes != RPROC_FILE_STORE_BUF_SIZE) { + return -EINVAL; + } + *image_data = fstore->buf; + return bytes; +} + +static void openamp_remoteproc_store_close(void *store) { + DEBUG_printf("store_close()\n"); + openamp_remoteproc_filestore_t *fstore = store; + mp_stream_close(fstore->file); + metal_free_memory(fstore->buf); + metal_free_memory(fstore); +} + +static int openamp_remoteproc_store_load(void *store, size_t offset, size_t size, + const void **data, metal_phys_addr_t pa, + struct metal_io_region *io, + char is_blocking) { + + int error = 0; + openamp_remoteproc_filestore_t *fstore = store; + + if (mp_stream_seek(fstore->file, offset, MP_SEEK_SET, &error) == -1) { + return -EINVAL; + } + + if (pa == METAL_BAD_PHYS) { + if (size > fstore->len) { + // Note tracked allocs don't support realloc. + fstore->len = size; + fstore->buf = metal_allocate_memory(size); + DEBUG_printf("store_load() realloc to %lu\n", fstore->len); + } + *data = fstore->buf; + DEBUG_printf("store_load(): pa 0x%lx offset %u size %u \n", (uint32_t)pa, offset, size); + } else { + void *va = metal_io_phys_to_virt(io, pa); + if (va == NULL) { + return -EINVAL; + } + *data = va; + DEBUG_printf("store_load(): pa 0x%lx va 0x%p offset %u size %u \n", (uint32_t)pa, va, offset, size); + } + + mp_uint_t bytes = mp_stream_read_exactly(fstore->file, (void *)*data, size, &error); + if (bytes != size || error != 0) { + return -EINVAL; + } + + return bytes; +} + +const struct image_store_ops openamp_remoteproc_store_ops = { + .open = openamp_remoteproc_store_open, + .close = openamp_remoteproc_store_close, + .load = openamp_remoteproc_store_load, + .features = SUPPORT_SEEK, +}; + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 89e68e248836..3066cac75d3b 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -321,6 +321,12 @@ static mp_obj_t network_cyw43_ifconfig(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_cyw43_ifconfig_obj, 1, 2, network_cyw43_ifconfig); +static mp_obj_t network_cyw43_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ipconfig(&self->cyw->netif[self->itf], n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_cyw43_ipconfig_obj, 1, network_cyw43_ipconfig); + static mp_obj_t network_cyw43_status(size_t n_args, const mp_obj_t *args) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); (void)self; @@ -532,10 +538,16 @@ static const mp_rom_map_elem_t network_cyw43_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_cyw43_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_cyw43_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_cyw43_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_cyw43_ipconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_cyw43_status_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_cyw43_config_obj) }, // Class constants. + { MP_ROM_QSTR(MP_QSTR_IF_STA), MP_ROM_INT(MOD_NETWORK_STA_IF) }, + { MP_ROM_QSTR(MP_QSTR_IF_AP), MP_ROM_INT(MOD_NETWORK_AP_IF) }, + { MP_ROM_QSTR(MP_QSTR_SEC_OPEN), MP_ROM_INT(CYW43_AUTH_OPEN) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA_WPA2), MP_ROM_INT(CYW43_AUTH_WPA2_MIXED_PSK) }, + { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(PM_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(PM_PERFORMANCE) }, { MP_ROM_QSTR(MP_QSTR_PM_POWERSAVE), MP_ROM_INT(PM_POWERSAVE) }, diff --git a/extmod/network_esp_hosted.c b/extmod/network_esp_hosted.c index 14e8be8b6a4e..0747343d87ea 100644 --- a/extmod/network_esp_hosted.c +++ b/extmod/network_esp_hosted.c @@ -204,6 +204,13 @@ static mp_obj_t network_esp_hosted_ifconfig(size_t n_args, const mp_obj_t *args) } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_esp_hosted_ifconfig_obj, 1, 2, network_esp_hosted_ifconfig); +static mp_obj_t network_esp_hosted_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + esp_hosted_obj_t *self = MP_OBJ_TO_PTR(args[0]); + void *netif = esp_hosted_wifi_get_netif(self->itf); + return mod_network_nic_ipconfig(netif, n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_esp_hosted_ipconfig_obj, 1, network_esp_hosted_ipconfig); + static mp_obj_t network_esp_hosted_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { esp_hosted_obj_t *self = MP_OBJ_TO_PTR(args[0]); @@ -299,8 +306,18 @@ static const mp_rom_map_elem_t network_esp_hosted_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_esp_hosted_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_esp_hosted_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_esp_hosted_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_esp_hosted_ipconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_esp_hosted_config_obj) }, { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_esp_hosted_status_obj) }, + + // Class constants. + { MP_ROM_QSTR(MP_QSTR_IF_STA), MP_ROM_INT(MOD_NETWORK_STA_IF) }, + { MP_ROM_QSTR(MP_QSTR_IF_AP), MP_ROM_INT(MOD_NETWORK_AP_IF) }, + { MP_ROM_QSTR(MP_QSTR_SEC_OPEN), MP_ROM_INT(ESP_HOSTED_SEC_OPEN) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WEP), MP_ROM_INT(ESP_HOSTED_SEC_WEP) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA_WPA2), MP_ROM_INT(ESP_HOSTED_SEC_WPA_WPA2_PSK) }, + + // For backwards compatibility. { MP_ROM_QSTR(MP_QSTR_OPEN), MP_ROM_INT(ESP_HOSTED_SEC_OPEN) }, { MP_ROM_QSTR(MP_QSTR_WEP), MP_ROM_INT(ESP_HOSTED_SEC_WEP) }, { MP_ROM_QSTR(MP_QSTR_WPA_PSK), MP_ROM_INT(ESP_HOSTED_SEC_WPA_WPA2_PSK) }, diff --git a/extmod/network_lwip.c b/extmod/network_lwip.c index caa30f6fff4d..97cb43902df6 100644 --- a/extmod/network_lwip.c +++ b/extmod/network_lwip.c @@ -26,6 +26,7 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "py/parsenum.h" #if MICROPY_PY_NETWORK && MICROPY_PY_LWIP @@ -40,9 +41,19 @@ #include "lwip/timeouts.h" #include "lwip/dns.h" #include "lwip/dhcp.h" +#include "lwip/nd6.h" +#include "lwip/dhcp6.h" +#include "lwip/prot/dhcp.h" +#include "lwip/prot/dhcp6.h" +#include +#include + +int mp_mod_network_prefer_dns_use_ip_version = 4; // Implementations of network methods that can be used by any interface. +// This function provides the implementation of nic.ifconfig, is deprecated and will be removed. +// Use network.ipconfig and nic.ipconfig instead. mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args) { if (n_args == 0) { // Get IP addresses @@ -90,6 +101,291 @@ mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_o } } +// This function provides the common implementation of network.ipconfig +mp_obj_t mod_network_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (kwargs->used == 0) { + // Get config value + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[0])) { + case MP_QSTR_dns: { + char addr_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(dns_getserver(0), addr_str, sizeof(addr_str)); + return mp_obj_new_str(addr_str, strlen(addr_str)); + } + case MP_QSTR_prefer: { + return MP_OBJ_NEW_SMALL_INT(mp_mod_network_prefer_dns_use_ip_version); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } else { + // Set config value(s) + if (n_args != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_dns: { + ip_addr_t dns; + size_t addr_len; + const char *addr_str = mp_obj_str_get_data(e->value, &addr_len); + if (!ipaddr_aton(addr_str, &dns)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments as dns server")); + } + dns_setserver(0, &dns); + break; + } + case MP_QSTR_prefer: { + int value = mp_obj_get_int(e->value); + if (value != 4 && value != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid prefer argument")); + } + mp_mod_network_prefer_dns_use_ip_version = value; + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + } + return mp_const_none; +} + +mp_obj_t mod_network_nic_ipconfig(struct netif *netif, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + + if (kwargs->used == 0) { + // Get config value + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[0])) { + case MP_QSTR_dhcp4: { + struct dhcp *dhcp = netif_dhcp_data(netif); + return mp_obj_new_bool(dhcp != NULL && dhcp->state != DHCP_STATE_OFF); + } + case MP_QSTR_has_dhcp4: { + return mp_obj_new_bool(dhcp_supplied_address(netif)); + } + #if LWIP_IPV6_DHCP6 + case MP_QSTR_dhcp6: { + struct dhcp6 *dhcp = netif_dhcp6_data(netif); + return mp_obj_new_bool(dhcp != NULL && dhcp->state != DHCP6_STATE_OFF); + } + #endif + #if LWIP_IPV6_AUTOCONFIG + case MP_QSTR_autoconf6: { + return netif->ip6_autoconfig_enabled ? mp_const_true : mp_const_false; + } + case MP_QSTR_has_autoconf6: { + int found = 0; + for (int i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + !netif_ip6_addr_isstatic(netif, i)) { + found = 1; + break; + } + } + if (found) { + break; + } + return mp_obj_new_bool(found); + } + #endif + case MP_QSTR_addr4: { + mp_obj_t tuple[2] = { + netutils_format_ipv4_addr((uint8_t *)&netif->ip_addr, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&netif->netmask, NETUTILS_BIG), + }; + return mp_obj_new_tuple(2, tuple); + } + #if LWIP_IPV6 + case MP_QSTR_addr6: { + mp_obj_t addrs[LWIP_IPV6_NUM_ADDRESSES]; + size_t n_addrs = 0; + for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { + char addr_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(netif_ip_addr6(netif, i), addr_str, sizeof(addr_str)); + mp_obj_t tuple[4] = { + mp_obj_new_str(addr_str, strlen(addr_str)), + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_state(netif, i)), + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_pref_life(netif, i)), // preferred + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_valid_life(netif, i)) + }; + addrs[n_addrs++] = mp_obj_new_tuple(4, tuple); + } + } + return mp_obj_new_list(n_addrs, addrs); + } + #endif + case MP_QSTR_gw4: { + return netutils_format_ipv4_addr((uint8_t *)&netif->gw, NETUTILS_BIG); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + return mp_const_none; + } else { + // Set config value(s) + if (n_args != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_dhcp4: { + if (mp_obj_is_true(e->value)) { + if (dhcp_supplied_address(netif)) { + dhcp_renew(netif); + } else { + dhcp_release_and_stop(netif); + dhcp_start(netif); + } + } else { + dhcp_release_and_stop(netif); + } + break; + } + #if LWIP_IPV6_DHCP6 + case MP_QSTR_dhcp6: { + dhcp6_disable(netif); + dhcp6_enable_stateless(netif); + break; + } + #endif + #if LWIP_IPV6_AUTOCONFIG + case MP_QSTR_autoconf6: { + netif_set_ip6_autoconfig_enabled(netif, mp_obj_is_true(e->value)); + if (mp_obj_is_true(e->value)) { + nd6_restart_netif(netif); + } else { + // Clear out any non-static addresses, skip link-local address in slot 0 + for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + !netif_ip6_addr_isstatic(netif, i)) { + netif_ip6_addr_set_state(netif, i, IP6_ADDR_INVALID); + } + } + } + break; + } + #endif + case MP_QSTR_addr4: + case MP_QSTR_addr6: { + ip_addr_t ip_addr; + ip_addr_t netmask; + int prefix_bits = 32; + if (e->value != mp_const_none && mp_obj_is_str(e->value)) { + size_t addr_len; + const char *input_str = mp_obj_str_get_data(e->value, &addr_len); + char plain_ip[IPADDR_STRLEN_MAX]; + char *split = strchr(input_str, '/'); + const char *addr_str = input_str; + if (split) { + int to_copy = sizeof(plain_ip) - 1; + if (split - addr_str < to_copy) { + to_copy = split - addr_str; + } + memcpy(plain_ip, addr_str, to_copy); + mp_obj_t prefix_obj = mp_parse_num_integer(split + 1, strlen(split + 1), 10, NULL); + prefix_bits = mp_obj_get_int(prefix_obj); + } + if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) { + uint32_t mask = -(1u << (32 - prefix_bits)); + ip_addr_set_ip4_u32_val(netmask, ((mask & 0xFF) << 24) | ((mask & 0xFF00) << 8) | ((mask >> 8) & 0xFF00) | ((mask >> 24) & 0xFF)); + } + if (!ipaddr_aton(addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if ((mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr6) != IP_IS_V6(&ip_addr) + || (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) != IP_IS_V4(&ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + } else if (e->value != mp_const_none) { + mp_obj_t *items; + mp_obj_get_array_fixed_n(e->value, 2, &items); + size_t addr_len; + const char *ip_addr_str = mp_obj_str_get_data(items[0], &addr_len); + const char *netmask_str = mp_obj_str_get_data(items[1], &addr_len); + if (!ipaddr_aton(ip_addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (!ipaddr_aton(netmask_str, &netmask)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (!IP_IS_V4(&ip_addr) || !IP_IS_V4(&netmask)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + } + if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) { + if (e->value != mp_const_none) { + netif->ip_addr = ip_addr; + netif->netmask = netmask; + } else { + ip4_addr_set_any(ip_2_ip4(&netif->ip_addr)); + ip4_addr_set_any(ip_2_ip4(&netif->netmask)); + } + #if LWIP_IPV6 + } else if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr6) { + // Clear out any existing static addresses. Address 0 comes from autoconf. + for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + netif_ip6_addr_isstatic(netif, i)) { + netif_ip6_addr_set_state(netif, i, IP6_ADDR_INVALID); + } + } + if (e->value != mp_const_none) { + s8_t free_idx; + netif_add_ip6_address(netif, ip_2_ip6(&ip_addr), &free_idx); + netif_ip6_addr_set_valid_life(netif, free_idx, IP6_ADDR_LIFE_STATIC); + netif_ip6_addr_set_pref_life(netif, free_idx, IP6_ADDR_LIFE_STATIC); + netif_ip6_addr_set_state(netif, free_idx, IP6_ADDR_PREFERRED); + } + #endif + } + break; + } + case MP_QSTR_gw4: { + ip_addr_t ip_addr; + size_t addr_len; + const char *addr_str = mp_obj_str_get_data(e->value, &addr_len); + if (!ipaddr_aton(addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (IP_IS_V4(&ip_addr)) { + netif->gw = ip_addr; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + } + return mp_const_none; +} + #endif // LWIP_VERSION_MAJOR >= 2 #endif // MICROPY_PY_NETWORK && MICROPY_PY_LWIP diff --git a/extmod/network_ninaw10.c b/extmod/network_ninaw10.c index e68a7a66e27d..926e228a35b8 100644 --- a/extmod/network_ninaw10.c +++ b/extmod/network_ninaw10.c @@ -153,7 +153,7 @@ static void network_ninaw10_poll_connect(mp_sched_node_t *node) { debug_printf("poll_connect() status: %d reason %d\n", status, reason); if (nina_connect(self->ssid, self->security, self->key, 0) != 0) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s\n"), + MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s"), self->ssid, self->security, self->key); } } else { @@ -193,19 +193,18 @@ static mp_obj_t network_ninaw10_active(size_t n_args, const mp_obj_t *args) { nina_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (n_args == 2) { bool active = mp_obj_is_true(args[1]); - network_ninaw10_deinit(); - if (active) { + if (active && !self->active) { int error = 0; if ((error = nina_init()) != 0) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("Failed to initialize Nina-W10 module, error: %d\n"), error); + MP_ERROR_TEXT("failed to initialize Nina-W10 module, error: %d"), error); } // check firmware version uint8_t semver[NINA_FW_VER_LEN]; if (nina_fw_version(semver) != 0) { nina_deinit(); mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("Failed to read firmware version, error: %d\n"), error); + MP_ERROR_TEXT("failed to read firmware version, error: %d"), error); } // Check the minimum supported firmware version. uint32_t fwmin = (NINA_FW_VER_MIN_MAJOR * 100) + @@ -218,12 +217,13 @@ static mp_obj_t network_ninaw10_active(size_t n_args, const mp_obj_t *args) { if (fwver < fwmin) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("Firmware version mismatch. Minimum supported firmware is v%d.%d.%d found v%d.%d.%d\n"), + MP_ERROR_TEXT("firmware version mismatch, minimum supported firmware is v%d.%d.%d found v%d.%d.%d"), NINA_FW_VER_MIN_MAJOR, NINA_FW_VER_MIN_MINOR, NINA_FW_VER_MIN_PATCH, semver[NINA_FW_VER_MAJOR_OFFS] - 48, semver[NINA_FW_VER_MINOR_OFFS] - 48, semver[NINA_FW_VER_PATCH_OFFS] - 48); } soft_timer_static_init(&mp_wifi_poll_timer, SOFT_TIMER_MODE_ONE_SHOT, 0, network_ninaw10_timer_callback); - } else { + } else if (!active && self->active) { + network_ninaw10_deinit(); nina_deinit(); } self->active = active; @@ -260,7 +260,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar static const mp_arg_t allowed_args[] = { { MP_QSTR_ssid, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_key, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_security, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NINA_SEC_WPA_PSK} }, + { MP_QSTR_security, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, }; @@ -273,20 +273,31 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar const char *ssid = mp_obj_str_get_str(args[ARG_ssid].u_obj); if (strlen(ssid) == 0) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SSID can't be empty!")); + mp_raise_ValueError(MP_ERROR_TEXT("SSID can't be empty")); } - // get key and sec + // get encryption key const char *key = NULL; - mp_uint_t security = NINA_SEC_OPEN; - if (args[ARG_key].u_obj != mp_const_none) { key = mp_obj_str_get_str(args[ARG_key].u_obj); - security = args[ARG_security].u_int; } + // get security mode + mp_uint_t security = args[ARG_security].u_int; + if (security == -1 && self->itf == MOD_NETWORK_STA_IF) { + security = NINA_SEC_WPA_PSK; + } else if (security == -1 && self->itf == MOD_NETWORK_AP_IF) { + security = NINA_SEC_WEP; + } + + // Ensure that the key is not empty if a security mode is used. if (security != NINA_SEC_OPEN && strlen(key) == 0) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Key can't be empty!")); + mp_raise_ValueError(MP_ERROR_TEXT("key can't be empty")); + } + + // Activate the interface if not active. + if (!self->active) { + network_ninaw10_active(2, (mp_obj_t [2]) { pos_args[0], mp_const_true }); } // Disconnect active connections first. @@ -298,7 +309,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar // Initialize WiFi in Station mode. if (nina_connect(ssid, security, key, 0) != 0) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s\n"), ssid, security, key); + MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s"), ssid, security, key); } // Save connection info to re-connect if needed. @@ -311,7 +322,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar mp_uint_t channel = args[ARG_channel].u_int; if (security != NINA_SEC_OPEN && security != NINA_SEC_WEP) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("AP mode supports WEP security only.")); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("AP mode only supports WEP or OPEN security modes")); } // Initialize WiFi in AP mode. @@ -849,11 +860,17 @@ static const mp_rom_map_elem_t nina_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_ninaw10_status_obj) }, { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&network_ninaw10_ioctl_obj) }, - // Network is not secured. + // Class constants. + { MP_ROM_QSTR(MP_QSTR_IF_STA), MP_ROM_INT(MOD_NETWORK_STA_IF) }, + { MP_ROM_QSTR(MP_QSTR_IF_AP), MP_ROM_INT(MOD_NETWORK_AP_IF) }, + + { MP_ROM_QSTR(MP_QSTR_SEC_OPEN), MP_ROM_INT(NINA_SEC_OPEN) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WEP), MP_ROM_INT(NINA_SEC_WEP) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA_WPA2), MP_ROM_INT(NINA_SEC_WPA_PSK) }, + + // For backwards compatibility. { MP_ROM_QSTR(MP_QSTR_OPEN), MP_ROM_INT(NINA_SEC_OPEN) }, - // Security type WEP (40 or 104). { MP_ROM_QSTR(MP_QSTR_WEP), MP_ROM_INT(NINA_SEC_WEP) }, - // Network secured with WPA/WPA2 personal(PSK). { MP_ROM_QSTR(MP_QSTR_WPA_PSK), MP_ROM_INT(NINA_SEC_WPA_PSK) }, }; diff --git a/extmod/network_wiznet5k.c b/extmod/network_wiznet5k.c index d1aadc3e1558..b8ca075b3d14 100644 --- a/extmod/network_wiznet5k.c +++ b/extmod/network_wiznet5k.c @@ -221,7 +221,7 @@ static void wiznet5k_init(void) { setSn_IMR(0, Sn_IR_RECV); #if _WIZCHIP_ == W5100S // Enable interrupt pin - setMR(MR2_G_IEN); + setMR2(getMR2() | MR2_G_IEN); #endif mp_hal_pin_input(wiznet5k_obj.pin_intn); @@ -915,6 +915,12 @@ static mp_obj_t wiznet5k_ifconfig(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(wiznet5k_ifconfig_obj, 1, 2, wiznet5k_ifconfig); +static mp_obj_t network_wiznet5k_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + wiznet5k_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ipconfig(&self->netif, n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(wiznet5k_ipconfig_obj, 1, network_wiznet5k_ipconfig); + static mp_obj_t send_ethernet_wrapper(mp_obj_t self_in, mp_obj_t buf_in) { wiznet5k_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_buffer_info_t buf; @@ -1008,6 +1014,9 @@ static const mp_rom_map_elem_t wiznet5k_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&wiznet5k_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&wiznet5k_active_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&wiznet5k_ifconfig_obj) }, + #if WIZNET5K_WITH_LWIP_STACK + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&wiznet5k_ipconfig_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&wiznet5k_status_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&wiznet5k_config_obj) }, #if WIZNET5K_WITH_LWIP_STACK diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 92bc6764edc6..8d555f1124f9 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -728,6 +728,9 @@ void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { } void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } switch (addr_mode) { case MP_BLUETOOTH_ADDRESS_MODE_PUBLIC: if (!has_public_address()) { diff --git a/extmod/os_dupterm.c b/extmod/os_dupterm.c index 156766a43c50..399f2237fbb2 100644 --- a/extmod/os_dupterm.c +++ b/extmod/os_dupterm.c @@ -45,6 +45,10 @@ void mp_os_deactivate(size_t dupterm_idx, const char *msg, mp_obj_t exc) { if (exc != MP_OBJ_NULL) { mp_obj_print_exception(&mp_plat_print, exc); } + if (term == MP_OBJ_NULL) { + // Dupterm was already closed. + return; + } nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_stream_close(term); diff --git a/lib/arduino-lib b/lib/arduino-lib new file mode 160000 index 000000000000..277efd50fcc6 --- /dev/null +++ b/lib/arduino-lib @@ -0,0 +1 @@ +Subproject commit 277efd50fcc637e2e2e87c9aa50c06acc9080970 diff --git a/lib/berkeley-db-1.xx b/lib/berkeley-db-1.xx index 35aaec4418ad..85373b548f1f 160000 --- a/lib/berkeley-db-1.xx +++ b/lib/berkeley-db-1.xx @@ -1 +1 @@ -Subproject commit 35aaec4418ad78628a3b935885dd189d41ce779b +Subproject commit 85373b548f1fb0119a463582570b44189dfb09ae diff --git a/lib/libmetal b/lib/libmetal new file mode 160000 index 000000000000..0cb7d293a7f2 --- /dev/null +++ b/lib/libmetal @@ -0,0 +1 @@ +Subproject commit 0cb7d293a7f25394a06847a28d0f0ace9862936e diff --git a/lib/open-amp b/lib/open-amp new file mode 160000 index 000000000000..1904dee18da8 --- /dev/null +++ b/lib/open-amp @@ -0,0 +1 @@ +Subproject commit 1904dee18da85400e72b8f55c5c99e872a486573 diff --git a/ports/cc3200/mpconfigport.h b/ports/cc3200/mpconfigport.h index 9cbc1afc0b09..f1ba4bedd002 100644 --- a/ports/cc3200/mpconfigport.h +++ b/ports/cc3200/mpconfigport.h @@ -127,6 +127,7 @@ #define MICROPY_PY_VFS (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/cc3200/mods/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_WDT (1) diff --git a/ports/esp32/boards/sdkconfig.spiram b/ports/esp32/boards/sdkconfig.spiram index f5503d554149..35fe3c676ddf 100644 --- a/ports/esp32/boards/sdkconfig.spiram +++ b/ports/esp32/boards/sdkconfig.spiram @@ -13,6 +13,6 @@ CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=8192 # to PSRAM bug workarounds. Apply some options to reduce the firmware size. CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y -CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y # Workaround required: see main_esp32/linker.lf CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index a3637870f4fe..89e46f9cfb3d 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -151,6 +151,8 @@ idf_component_register( ${MICROPY_PORT_DIR} ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} + LDFRAGMENTS + linker.lf REQUIRES ${IDF_COMPONENTS} ) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 6fb15d3cc890..62dd7ae3cccf 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -116,6 +116,10 @@ void mp_task(void *pvParameter) { } void *mp_task_heap = MP_PLAT_ALLOC_HEAP(MICROPY_GC_INITIAL_HEAP_SIZE); + if (mp_task_heap == NULL) { + printf("mp_task_heap allocation failed!\n"); + esp_restart(); + } soft_reset: // initialise the stack pointer for the main thread diff --git a/ports/esp32/main_esp32/linker.lf b/ports/esp32/main_esp32/linker.lf new file mode 100644 index 000000000000..e00cd63f5548 --- /dev/null +++ b/ports/esp32/main_esp32/linker.lf @@ -0,0 +1,39 @@ +# This fixes components/esp_ringbuf/linker.lf to allow us to put non-ISR ringbuf functions in flash. + +# Requires that both RINGBUF_PLACE_FUNCTIONS_INTO_FLASH and RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH +# are set to "n" (which is the default), otherwise this would result in duplicate section config +# when resolving the linker fragments. + +# The effect of this file is to leave the ISR functions in RAM (which we require), but apply a fixed +# version of RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y (leaving out prvGetFreeSize and prvGetCurMaxSizeByteBuf) +# See https://github.com/espressif/esp-idf/issues/13378 + +[mapping:esp_ringbuf_fix] +archive: libesp_ringbuf.a +entries: + # This is exactly the list of functions from RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y, + # but with prvGetFreeSize and prvGetCurMaxSizeByteBuf removed. + ringbuf: prvGetCurMaxSizeNoSplit (default) + ringbuf: prvGetCurMaxSizeAllowSplit (default) + ringbuf: prvInitializeNewRingbuffer (default) + ringbuf: prvReceiveGeneric (default) + ringbuf: vRingbufferDelete (default) + ringbuf: vRingbufferGetInfo (default) + ringbuf: vRingbufferReturnItem (default) + ringbuf: xRingbufferAddToQueueSetRead (default) + ringbuf: xRingbufferCanRead (default) + ringbuf: xRingbufferCreate (default) + ringbuf: xRingbufferCreateStatic (default) + ringbuf: xRingbufferCreateNoSplit (default) + ringbuf: xRingbufferReceive (default) + ringbuf: xRingbufferReceiveSplit (default) + ringbuf: xRingbufferReceiveUpTo (default) + ringbuf: xRingbufferRemoveFromQueueSetRead (default) + ringbuf: xRingbufferSend (default) + ringbuf: xRingbufferSendAcquire (default) + ringbuf: xRingbufferSendComplete (default) + ringbuf: xRingbufferPrintInfo (default) + ringbuf: xRingbufferGetMaxItemSize (default) + ringbuf: xRingbufferGetCurFreeSize (default) + + # Everything else will have the default rule already applied (i.e. noflash_text). diff --git a/ports/esp32/main_esp32c3/linker.lf b/ports/esp32/main_esp32c3/linker.lf new file mode 100644 index 000000000000..31c5b4563ca9 --- /dev/null +++ b/ports/esp32/main_esp32c3/linker.lf @@ -0,0 +1 @@ +# Empty linker fragment (no workaround required for C3, see main_esp32/linker.lf). diff --git a/ports/esp32/main_esp32s2/linker.lf b/ports/esp32/main_esp32s2/linker.lf new file mode 100644 index 000000000000..3c496fa878b3 --- /dev/null +++ b/ports/esp32/main_esp32s2/linker.lf @@ -0,0 +1 @@ +# Empty linker fragment (no workaround required for S2, see main_esp32/linker.lf). diff --git a/ports/esp32/main_esp32s3/linker.lf b/ports/esp32/main_esp32s3/linker.lf new file mode 100644 index 000000000000..81d27906be0e --- /dev/null +++ b/ports/esp32/main_esp32s3/linker.lf @@ -0,0 +1 @@ +# Empty linker fragment (no workaround required for S3, see main_esp32/linker.lf). diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index f004c78b0aef..0afb12f85c1a 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -114,6 +114,7 @@ #define MICROPY_PY_OS_URANDOM (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/esp32/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index ece88e705f94..eb2bcda03e1b 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -730,6 +730,20 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_network_ifconfig_obj) }, // Constants + { MP_ROM_QSTR(MP_QSTR_IF_STA), MP_ROM_INT(WIFI_IF_STA)}, + { MP_ROM_QSTR(MP_QSTR_IF_AP), MP_ROM_INT(WIFI_IF_AP)}, + + { MP_ROM_QSTR(MP_QSTR_SEC_OPEN), MP_ROM_INT(WIFI_AUTH_OPEN) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WEP), MP_ROM_INT(WIFI_AUTH_WEP) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA), MP_ROM_INT(WIFI_AUTH_WPA_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA2), MP_ROM_INT(WIFI_AUTH_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA_WPA2), MP_ROM_INT(WIFI_AUTH_WPA_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_ENT), MP_ROM_INT(WIFI_AUTH_WPA2_ENTERPRISE) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3), MP_ROM_INT(WIFI_AUTH_WPA3_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WAPI), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_OWE), MP_ROM_INT(WIFI_AUTH_OWE) }, + { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, { MP_ROM_QSTR(MP_QSTR_PM_POWERSAVE), MP_ROM_INT(WIFI_PS_MAX_MODEM) }, diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index 32eb39a203e7..1b9e9623b629 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -38,7 +38,7 @@ MICROPY_ROM_TEXT_COMPRESSION ?= 1 MICROPY_PY_SSL = 1 MICROPY_SSL_AXTLS = 1 AXTLS_DEFS_EXTRA = -Dabort=abort_ -DRT_MAX_PLAIN_LENGTH=1024 -DRT_EXTRA=4096 -BTREE_DEFS_EXTRA = -DDEFPSIZE=1024 -DMINCACHE=3 +BTREE_DEFS_EXTRA = -DMICROPY_BERKELEY_DB_DEFPSIZE=1024 -DMICROPY_BERKELEY_DB_MINCACHE=3 FROZEN_MANIFEST ?= boards/manifest.py @@ -96,6 +96,9 @@ COPT += -Os -DNDEBUG LDFLAGS += --gc-sections endif +# Flags for optional C++ source code +CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS)) + # Options for mpy-cross MPY_CROSS_FLAGS += -march=xtensa diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index bbfab64fba8c..6504127755e8 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -66,6 +66,7 @@ #define MICROPY_PY_LWIP_SOCK_RAW (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/esp8266/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/esp8266/network_wlan.c b/ports/esp8266/network_wlan.c index 5838cc8bc6a8..af1611a2496e 100644 --- a/ports/esp8266/network_wlan.c +++ b/ports/esp8266/network_wlan.c @@ -516,6 +516,15 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_ifconfig_obj) }, // Constants + { MP_ROM_QSTR(MP_QSTR_IF_STA), MP_ROM_INT(STATION_IF)}, + { MP_ROM_QSTR(MP_QSTR_IF_AP), MP_ROM_INT(SOFTAP_IF)}, + + { MP_ROM_QSTR(MP_QSTR_SEC_OPEN), MP_ROM_INT(AUTH_OPEN) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WEP), MP_ROM_INT(AUTH_WEP) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA), MP_ROM_INT(AUTH_WPA_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA2), MP_ROM_INT(AUTH_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA_WPA2), MP_ROM_INT(AUTH_WPA_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(NONE_SLEEP_T) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(MODEM_SLEEP_T) }, { MP_ROM_QSTR(MP_QSTR_PM_POWERSAVE), MP_ROM_INT(LIGHT_SLEEP_T) }, diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 761c491742bc..adb071a7fee8 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -87,6 +87,7 @@ int main(void) { memcpy(&buf[0], "PYBD", 4); mp_hal_get_mac_ascii(MP_HAL_MAC_WLAN0, 8, 4, (char *)&buf[4]); cyw43_wifi_ap_set_ssid(&cyw43_state, 8, buf); + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_WPA2_MIXED_PSK); cyw43_wifi_ap_set_password(&cyw43_state, 8, (const uint8_t *)"pybd0123"); } #endif @@ -115,7 +116,9 @@ int main(void) { // Execute user scripts. int ret = pyexec_file_if_exists("boot.py"); + #if MICROPY_HW_ENABLE_USBDEV mp_usbd_init(); + #endif if (ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index c67b010c01ac..a4c6d6e2060b 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -79,6 +79,7 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (trng_random_u32()) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/mimxrt/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) @@ -130,9 +131,9 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_WEBSOCKET (MICROPY_PY_LWIP) #define MICROPY_PY_WEBREPL (MICROPY_PY_LWIP) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) -// #define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) +#define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) #define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) -// #define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) +#define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) #ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) @@ -146,6 +147,8 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-mimxrt" #endif +#define MICROPY_HW_ENABLE_USBDEV (1) + // Hooks to add builtins #if defined(IOMUX_TABLE_ENET) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 7150aa6399b0..4fe921f0fad8 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -9,8 +9,8 @@ BOARD ?= PCA10040 BOARD_DIR ?= boards/$(BOARD) endif -ifeq ($(wildcard boards/$(BOARD)/.),) -$(error Invalid BOARD specified) +ifeq ($(wildcard $(BOARD_DIR)/.),) +$(error Invalid BOARD specified: $(BOARD_DIR)) endif # If SoftDevice is selected, try to use that one. @@ -19,7 +19,7 @@ SD_LOWER = $(shell echo $(SD) | tr '[:upper:]' '[:lower:]') # TODO: Verify that it is a valid target. -include boards/$(BOARD)/mpconfigboard.mk +include $(BOARD_DIR)/mpconfigboard.mk ifeq ($(SD), ) # If the build directory is not given, make it reflect the board name. @@ -48,7 +48,7 @@ ifneq ($(LD_FILE),) LD_FILES = $(LD_FILE) endif --include boards/$(BOARD)/modules/boardmodules.mk +-include $(BOARD_DIR)/modules/boardmodules.mk # qstr definitions (must come before including py.mk) QSTR_DEFS = qstrdefsport.h @@ -113,7 +113,7 @@ NRF_DEFINES += -D$(MCU_SUB_VARIANT_UPPER) NRF_DEFINES += -DCONFIG_GPIO_AS_PINRESET MAKE_PINS = boards/make-pins.py -BOARD_PINS = boards/$(BOARD)/pins.csv +BOARD_PINS = $(BOARD_DIR)/pins.csv AF_FILE = $(MCU_VARIANT)_af.csv PREFIX_FILE = boards/$(MCU_VARIANT)_prefix.c GEN_PINS_SRC = $(BUILD)/pins_gen.c @@ -139,7 +139,7 @@ endif CFLAGS += $(CFLAGS_MCU_$(MCU_SERIES)) CFLAGS += $(INC) -Wall -Werror -ansi -std=c11 -nostdlib $(COPT) $(NRF_DEFINES) $(CFLAGS_EXTRA) CFLAGS += -fno-strict-aliasing -CFLAGS += -Iboards/$(BOARD) +CFLAGS += -I$(BOARD_DIR) CFLAGS += -DNRF5_HAL_H='<$(MCU_VARIANT)_hal.h>' LDFLAGS += $(CFLAGS) @@ -163,8 +163,6 @@ CFLAGS += -Os -DNDEBUG LDFLAGS += -Os endif -LIBS = \ - ifeq ($(MCU_VARIANT), nrf52) SRC_LIB_C += $(SRC_LIB_LIBM_C) @@ -481,7 +479,7 @@ $(OBJ): | $(HEADER_BUILD)/pins.h # Use a pattern rule here so that make will only call make-pins.py once to make # both pins_gen.c and pins.h -$(BUILD)/%_gen.c $(HEADER_BUILD)/%.h $(HEADER_BUILD)/%_af_const.h: boards/$(BOARD)/%.csv $(MAKE_PINS) $(AF_FILE) $(PREFIX_FILE) | $(HEADER_BUILD) +$(BUILD)/%_gen.c $(HEADER_BUILD)/%.h $(HEADER_BUILD)/%_af_const.h: $(BOARD_DIR)/%.csv $(MAKE_PINS) $(AF_FILE) $(PREFIX_FILE) | $(HEADER_BUILD) $(ECHO) "Create $@" $(Q)$(PYTHON) $(MAKE_PINS) --board-csv $(BOARD_PINS) --af-csv $(AF_FILE) --prefix $(PREFIX_FILE) \ --output-source $(GEN_PINS_SRC) --output-header $(GEN_PINS_HDR) --output-af-const $(GEN_PINS_AF_CONST) diff --git a/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h index 94624be028a1..499abbc4efc3 100644 --- a/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h +++ b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h index 343fa4c2f394..35b70d61290f 100644 --- a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h +++ b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/boards/PCA10059/mpconfigboard.h b/ports/nrf/boards/PCA10059/mpconfigboard.h index 1c87731584de..97f6ccb9410f 100644 --- a/ports/nrf/boards/PCA10059/mpconfigboard.h +++ b/ports/nrf/boards/PCA10059/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index dd9f232b80c5..07bf3e8adb5c 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -262,7 +262,7 @@ void NORETURN _start(void) { led_state(1, 0); #if MICROPY_VFS || MICROPY_MBFS || MICROPY_MODULE_FROZEN - ret = pyexec_file_if_exists("boot.py"); + ret_code = pyexec_file_if_exists("boot.py"); #endif #if MICROPY_HW_USB_CDC @@ -270,7 +270,7 @@ void NORETURN _start(void) { #endif #if MICROPY_VFS || MICROPY_MBFS || MICROPY_MODULE_FROZEN - if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL && ret != 0) { + if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL && ret_code != 0) { pyexec_file_if_exists("main.py"); } #endif diff --git a/ports/nrf/modules/machine/adc.c b/ports/nrf/modules/machine/adc.c index c45d0ba5ffda..9fa8005a2280 100644 --- a/ports/nrf/modules/machine/adc.c +++ b/ports/nrf/modules/machine/adc.c @@ -38,14 +38,14 @@ typedef struct _machine_adc_obj_t { mp_obj_base_t base; - uint8_t id; -#if NRF51 - uint8_t ain; -#endif + uint8_t id; + #if NRF51 + uint8_t ain; + #endif } machine_adc_obj_t; static const machine_adc_obj_t machine_adc_obj[] = { -#if NRF51 + #if NRF51 {{&machine_adc_type}, .id = 0, .ain = NRF_ADC_CONFIG_INPUT_0}, {{&machine_adc_type}, .id = 1, .ain = NRF_ADC_CONFIG_INPUT_1}, {{&machine_adc_type}, .id = 2, .ain = NRF_ADC_CONFIG_INPUT_2}, @@ -54,7 +54,7 @@ static const machine_adc_obj_t machine_adc_obj[] = { {{&machine_adc_type}, .id = 5, .ain = NRF_ADC_CONFIG_INPUT_5}, {{&machine_adc_type}, .id = 6, .ain = NRF_ADC_CONFIG_INPUT_6}, {{&machine_adc_type}, .id = 7, .ain = NRF_ADC_CONFIG_INPUT_7}, -#else + #else {{&machine_adc_type}, .id = 0}, {{&machine_adc_type}, .id = 1}, {{&machine_adc_type}, .id = 2}, @@ -63,14 +63,14 @@ static const machine_adc_obj_t machine_adc_obj[] = { {{&machine_adc_type}, .id = 5}, {{&machine_adc_type}, .id = 6}, {{&machine_adc_type}, .id = 7}, -#endif + #endif }; void adc_init0(void) { -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) const uint8_t interrupt_priority = 6; nrfx_saadc_init(interrupt_priority); -#endif + #endif } static int adc_find(mp_obj_t id) { @@ -124,49 +124,49 @@ static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args int adc_id = adc_find(args[ARG_id].u_obj); const machine_adc_obj_t *self = &machine_adc_obj[adc_id]; -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) const nrfx_saadc_channel_t config = { \ .channel_config = { .resistor_p = NRF_SAADC_RESISTOR_DISABLED, .resistor_n = NRF_SAADC_RESISTOR_DISABLED, - .gain = NRF_SAADC_GAIN1_4, - .reference = NRF_SAADC_REFERENCE_VDD4, - .acq_time = NRF_SAADC_ACQTIME_3US, - .mode = NRF_SAADC_MODE_SINGLE_ENDED, - .burst = NRF_SAADC_BURST_DISABLED, + .gain = NRF_SAADC_GAIN1_4, + .reference = NRF_SAADC_REFERENCE_VDD4, + .acq_time = NRF_SAADC_ACQTIME_3US, + .mode = NRF_SAADC_MODE_SINGLE_ENDED, + .burst = NRF_SAADC_BURST_DISABLED, }, - .pin_p = (nrf_saadc_input_t)(1 + self->id), // pin_p=0 is AIN0, pin_p=8 is AIN7 - .pin_n = NRF_SAADC_INPUT_DISABLED, - .channel_index = self->id, + .pin_p = (nrf_saadc_input_t)(1 + self->id), // pin_p=0 is AIN0, pin_p=8 is AIN7 + .pin_n = NRF_SAADC_INPUT_DISABLED, + .channel_index = self->id, }; nrfx_saadc_channels_config(&config, 1); -#endif + #endif return MP_OBJ_FROM_PTR(self); } -int16_t machine_adc_value_read(machine_adc_obj_t * adc_obj) { +int16_t machine_adc_value_read(machine_adc_obj_t *adc_obj) { -#if NRF51 + #if NRF51 nrf_adc_value_t value = 0; nrfx_adc_channel_t channel_config = { .config.resolution = NRF_ADC_CONFIG_RES_8BIT, - .config.input = NRF_ADC_CONFIG_SCALING_INPUT_TWO_THIRDS, - .config.reference = NRF_ADC_CONFIG_REF_VBG, - .config.input = adc_obj->ain, - .config.extref = ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos // Currently not defined in nrfx/hal. + .config.input = NRF_ADC_CONFIG_SCALING_INPUT_TWO_THIRDS, + .config.reference = NRF_ADC_CONFIG_REF_VBG, + .config.input = adc_obj->ain, + .config.extref = ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos // Currently not defined in nrfx/hal. }; nrfx_adc_sample_convert(&channel_config, &value); -#else // NRF52 + #else // NRF52 nrf_saadc_value_t value = 0; nrfx_saadc_simple_mode_set((1 << adc_obj->id), NRF_SAADC_RESOLUTION_8BIT, NRF_SAADC_INPUT_DISABLED, NULL); nrfx_saadc_buffer_set(&value, 1); nrfx_saadc_mode_trigger(); -#endif + #endif return value; } @@ -209,10 +209,9 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mp_machine_adc_value_obj, machine_adc_value); #define DIODE_VOLT_DROP_MILLIVOLT (270) // Voltage drop over diode. #define BATTERY_MILLIVOLT(VALUE) \ - ((((VALUE) * ADC_REF_VOLTAGE_IN_MILLIVOLT) / 255) * ADC_PRE_SCALING_MULTIPLIER) + ((((VALUE)*ADC_REF_VOLTAGE_IN_MILLIVOLT) / 255) * ADC_PRE_SCALING_MULTIPLIER) -static uint8_t battery_level_in_percent(const uint16_t mvolts) -{ +static uint8_t battery_level_in_percent(const uint16_t mvolts) { uint8_t battery_level; if (mvolts >= 3000) { @@ -236,19 +235,19 @@ static uint8_t battery_level_in_percent(const uint16_t mvolts) /// Get battery level in percentage. mp_obj_t machine_adc_battery_level(void) { -#if NRF51 + #if NRF51 nrf_adc_value_t value = 0; nrfx_adc_channel_t channel_config = { .config.resolution = NRF_ADC_CONFIG_RES_8BIT, - .config.input = NRF_ADC_CONFIG_SCALING_SUPPLY_ONE_THIRD, - .config.reference = NRF_ADC_CONFIG_REF_VBG, - .config.input = NRF_ADC_CONFIG_INPUT_DISABLED, - .config.extref = ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos // Currently not defined in nrfx/hal. + .config.input = NRF_ADC_CONFIG_SCALING_SUPPLY_ONE_THIRD, + .config.reference = NRF_ADC_CONFIG_REF_VBG, + .config.input = NRF_ADC_CONFIG_INPUT_DISABLED, + .config.extref = ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos // Currently not defined in nrfx/hal. }; nrfx_adc_sample_convert(&channel_config, &value); -#else // NRF52 + #else // NRF52 nrf_saadc_value_t value = 0; const nrfx_saadc_channel_t config = { \ @@ -256,22 +255,22 @@ mp_obj_t machine_adc_battery_level(void) { { .resistor_p = NRF_SAADC_RESISTOR_DISABLED, .resistor_n = NRF_SAADC_RESISTOR_DISABLED, - .gain = NRF_SAADC_GAIN1_6, - .reference = NRF_SAADC_REFERENCE_INTERNAL, - .acq_time = NRF_SAADC_ACQTIME_3US, - .mode = NRF_SAADC_MODE_SINGLE_ENDED, - .burst = NRF_SAADC_BURST_DISABLED, + .gain = NRF_SAADC_GAIN1_6, + .reference = NRF_SAADC_REFERENCE_INTERNAL, + .acq_time = NRF_SAADC_ACQTIME_3US, + .mode = NRF_SAADC_MODE_SINGLE_ENDED, + .burst = NRF_SAADC_BURST_DISABLED, }, - .pin_p = NRF_SAADC_INPUT_VDD, - .pin_n = NRF_SAADC_INPUT_DISABLED, - .channel_index = 0, + .pin_p = NRF_SAADC_INPUT_VDD, + .pin_n = NRF_SAADC_INPUT_DISABLED, + .channel_index = 0, }; nrfx_saadc_channels_config(&config, 1); nrfx_saadc_simple_mode_set((1 << 0), NRF_SAADC_RESOLUTION_8BIT, NRF_SAADC_INPUT_DISABLED, NULL); nrfx_saadc_buffer_set(&value, 1); nrfx_saadc_mode_trigger(); -#endif + #endif uint16_t batt_lvl_in_milli_volts = BATTERY_MILLIVOLT(value) + DIODE_VOLT_DROP_MILLIVOLT; uint16_t batt_in_percent = battery_level_in_percent(batt_lvl_in_milli_volts); diff --git a/ports/nrf/modules/machine/adc.h b/ports/nrf/modules/machine/adc.h index 84e5cdbb85f3..9ef1b470447b 100644 --- a/ports/nrf/modules/machine/adc.h +++ b/ports/nrf/modules/machine/adc.h @@ -31,6 +31,6 @@ typedef struct _machine_adc_obj_t machine_adc_obj_t; void adc_init0(void); -int16_t machine_adc_value_read(machine_adc_obj_t * adc_obj); +int16_t machine_adc_value_read(machine_adc_obj_t *adc_obj); #endif // ADC_H__ diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index 7e488366d70b..6c2b3e94838e 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -68,7 +68,7 @@ typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; - nrfx_twi_t p_twi; // Driver instance + nrfx_twi_t p_twi; // Driver instance } machine_hard_i2c_obj_t; static const machine_hard_i2c_obj_t machine_hard_i2c_obj[] = { @@ -159,8 +159,7 @@ int machine_hard_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, size if (err_code != NRFX_SUCCESS) { if (err_code == NRFX_ERROR_DRV_TWI_ERR_ANACK) { return -MP_ENODEV; - } - else if (err_code == NRFX_ERROR_DRV_TWI_ERR_DNACK) { + } else if (err_code == NRFX_ERROR_DRV_TWI_ERR_DNACK) { return -MP_EIO; } return -MP_ETIMEDOUT; diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index 9ad8d606a265..de1d0e31246b 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -116,16 +116,16 @@ void machine_init(void) { reset_cause = PYB_RESET_LOCKUP; } else if (state & POWER_RESETREAS_OFF_Msk) { reset_cause = PYB_RESET_POWER_ON; -#if !defined(NRF9160_XXAA) + #if !defined(NRF9160_XXAA) } else if (state & POWER_RESETREAS_LPCOMP_Msk) { reset_cause = PYB_RESET_LPCOMP; -#endif + #endif } else if (state & POWER_RESETREAS_DIF_Msk) { reset_cause = PYB_RESET_DIF; -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) } else if (state & POWER_RESETREAS_NFC_Msk) { reset_cause = PYB_RESET_NFC; -#endif + #endif } // clear reset reason @@ -216,22 +216,22 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { } static mp_obj_t machine_enable_irq(void) { -#ifndef BLUETOOTH_SD + #ifndef BLUETOOTH_SD __enable_irq(); -#else + #else -#endif + #endif return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_0(machine_enable_irq_obj, machine_enable_irq); // Resets the board in a manner similar to pushing the external RESET button. static mp_obj_t machine_disable_irq(void) { -#ifndef BLUETOOTH_SD + #ifndef BLUETOOTH_SD __disable_irq(); -#else + #else -#endif + #endif return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); diff --git a/ports/nrf/modules/machine/pin.c b/ports/nrf/modules/machine/pin.c index e72a16eee7c2..2191cc9521d0 100644 --- a/ports/nrf/modules/machine/pin.c +++ b/ports/nrf/modules/machine/pin.c @@ -33,6 +33,7 @@ #include "py/nlr.h" #include "py/runtime.h" #include "py/mphal.h" +#include "py/gc.h" #include "pin.h" #include "nrf_gpio.h" #include "nrfx_gpiote.h" @@ -234,9 +235,9 @@ static void pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t } mp_printf(print, "Pin(%d, mode=%s, pull=%s)", - self->pin, - (nrf_gpio_pin_dir_get(self->pin) == NRF_GPIO_PIN_DIR_OUTPUT) ? "OUT" : "IN", - pull); + self->pin, + (nrf_gpio_pin_dir_get(self->pin) == NRF_GPIO_PIN_DIR_OUTPUT) ? "OUT" : "IN", + pull); } static mp_obj_t pin_obj_init_helper(const pin_obj_t *pin, mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kw_args); @@ -375,11 +376,11 @@ static mp_obj_t pin_obj_init_helper(const pin_obj_t *self, mp_uint_t n_args, con if (mode == NRF_GPIO_PIN_DIR_OUTPUT || mode == NRF_GPIO_PIN_DIR_INPUT) { nrf_gpio_cfg(self->pin, - mode, - input, - pull, - NRF_GPIO_PIN_S0S1, - NRF_GPIO_PIN_NOSENSE); + mode, + input, + pull, + NRF_GPIO_PIN_S0S1, + NRF_GPIO_PIN_NOSENSE); } else { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin mode: %d"), mode); } @@ -496,9 +497,31 @@ static MP_DEFINE_CONST_FUN_OBJ_1(pin_af_obj, pin_af); static void pin_common_irq_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { mp_obj_t pin_handler = MP_STATE_PORT(pin_irq_handlers)[pin]; mp_obj_t pin_number = MP_OBJ_NEW_SMALL_INT(pin); - const pin_obj_t *pin_obj = pin_find(pin_number); - - mp_call_function_1(pin_handler, (mp_obj_t)pin_obj); + const pin_obj_t *pin_obj = pin_find(pin_number); + + if (pin_handler != mp_const_none) { + #if MICROPY_ENABLE_SCHEDULER + mp_sched_lock(); + #endif + // When executing code within a handler we must lock the GC to prevent + // any memory allocations. We must also catch any exceptions. + gc_lock(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_call_function_1(pin_handler, (mp_obj_t)pin_obj); + nlr_pop(); + } else { + // Uncaught exception; disable the callback so it doesn't run again. + MP_STATE_PORT(pin_irq_handlers)[pin] = mp_const_none; + nrfx_gpiote_in_uninit(pin); + mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in interrupt handler for Pin('%q')\n", pin_obj->name); + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + gc_unlock(); + #if MICROPY_ENABLE_SCHEDULER + mp_sched_unlock(); + #endif + } } static mp_obj_t pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -591,7 +614,7 @@ static const mp_rom_map_elem_t pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_AF_OD), MP_ROM_INT(GPIO_MODE_AF_OD) }, { MP_ROM_QSTR(MP_QSTR_PULL_NONE), MP_ROM_INT(GPIO_NOPULL) }, */ -#include "genhdr/pins_af_const.h" + #include "genhdr/pins_af_const.h" }; static MP_DEFINE_CONST_DICT(pin_locals_dict, pin_locals_dict_table); diff --git a/ports/nrf/modules/machine/pin.h b/ports/nrf/modules/machine/pin.h index 7004b320b1b6..41579011b5d8 100644 --- a/ports/nrf/modules/machine/pin.h +++ b/ports/nrf/modules/machine/pin.h @@ -34,43 +34,43 @@ #include "py/obj.h" typedef struct { - mp_obj_base_t base; - qstr name; - uint8_t idx; - uint8_t fn; - uint8_t unit; - uint8_t type; - - union { - void *reg; - - PIN_DEFS_PORT_AF_UNION - }; + mp_obj_base_t base; + qstr name; + uint8_t idx; + uint8_t fn; + uint8_t unit; + uint8_t type; + + union { + void *reg; + + PIN_DEFS_PORT_AF_UNION + }; } pin_af_obj_t; typedef struct { - mp_obj_base_t base; - qstr name; - uint32_t pin : 8; - uint32_t num_af : 4; - uint32_t adc_channel : 5; // Some ARM processors use 32 bits/PORT - uint32_t adc_num : 3; // 1 bit per ADC - const pin_af_obj_t *af; - uint32_t pull; + mp_obj_base_t base; + qstr name; + uint32_t pin : 8; + uint32_t num_af : 4; + uint32_t adc_channel : 5; // Some ARM processors use 32 bits/PORT + uint32_t adc_num : 3; // 1 bit per ADC + const pin_af_obj_t *af; + uint32_t pull; } pin_obj_t; extern const mp_obj_type_t pin_type; extern const mp_obj_type_t pin_af_type; typedef struct { - const char *name; - const pin_obj_t *pin; + const char *name; + const pin_obj_t *pin; } pin_named_pin_t; extern const pin_named_pin_t pin_board_pins[]; extern const pin_named_pin_t pin_cpu_pins[]; -//extern pin_map_obj_t pin_map_obj; +// extern pin_map_obj_t pin_map_obj; typedef struct { mp_obj_base_t base; diff --git a/ports/nrf/modules/machine/pwm.c b/ports/nrf/modules/machine/pwm.c index 393d1061799d..8145509c7628 100644 --- a/ports/nrf/modules/machine/pwm.c +++ b/ports/nrf/modules/machine/pwm.c @@ -233,6 +233,11 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args self->p_config->mode[pwm_channel] = MODE_HIGH_LOW; self->p_config->defer_start = false; + // Allocate the device if it was not used before. + if (hard_configs[pwm_id].active == FREE) { + hard_configs[pwm_id].active = STOPPED; + } + // start the PWM running for this channel mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, all_args + n_args); diff --git a/ports/nrf/modules/machine/rtcounter.c b/ports/nrf/modules/machine/rtcounter.c index d85db9b9b7aa..c1ef1b4dd977 100644 --- a/ports/nrf/modules/machine/rtcounter.c +++ b/ports/nrf/modules/machine/rtcounter.c @@ -27,6 +27,7 @@ #include "py/nlr.h" #include "py/runtime.h" +#include "mphalport.h" #include "rtcounter.h" #include "nrfx_rtc.h" #include "nrf_clock.h" @@ -49,18 +50,18 @@ typedef struct { // Non-volatile part of the RTCounter object. typedef struct _machine_rtc_obj_t { - mp_obj_base_t base; - const nrfx_rtc_t * p_rtc; // Driver instance - nrfx_rtc_handler_t handler; // interrupt callback - machine_rtc_config_t * config; // pointer to volatile part + mp_obj_base_t base; + const nrfx_rtc_t *p_rtc; // Driver instance + nrfx_rtc_handler_t handler; // interrupt callback + machine_rtc_config_t *config; // pointer to volatile part } machine_rtc_obj_t; static const nrfx_rtc_t machine_rtc_instances[] = { NRFX_RTC_INSTANCE(0), NRFX_RTC_INSTANCE(1), -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) NRFX_RTC_INSTANCE(2), -#endif + #endif }; static machine_rtc_config_t configs[MP_ARRAY_SIZE(machine_rtc_instances)]; @@ -72,15 +73,15 @@ static void interrupt_handler2(nrfx_rtc_int_type_t int_type); #endif static const machine_rtc_obj_t machine_rtc_obj[] = { - {{&machine_rtcounter_type}, .p_rtc = &machine_rtc_instances[0], .handler=interrupt_handler0, .config=&configs[0]}, - {{&machine_rtcounter_type}, .p_rtc = &machine_rtc_instances[1], .handler=interrupt_handler1, .config=&configs[1]}, -#if defined(NRF52_SERIES) - {{&machine_rtcounter_type}, .p_rtc = &machine_rtc_instances[2], .handler=interrupt_handler2, .config=&configs[2]}, -#endif + {{&machine_rtcounter_type}, .p_rtc = &machine_rtc_instances[0], .handler = interrupt_handler0, .config = &configs[0]}, + {{&machine_rtcounter_type}, .p_rtc = &machine_rtc_instances[1], .handler = interrupt_handler1, .config = &configs[1]}, + #if defined(NRF52_SERIES) + {{&machine_rtcounter_type}, .p_rtc = &machine_rtc_instances[2], .handler = interrupt_handler2, .config = &configs[2]}, + #endif }; static void interrupt_handler(size_t instance_id) { - const machine_rtc_obj_t * self = &machine_rtc_obj[instance_id]; + const machine_rtc_obj_t *self = &machine_rtc_obj[instance_id]; machine_rtc_config_t *config = self->config; if (config->callback != NULL) { mp_call_function_1((mp_obj_t)config->callback, (mp_obj_t)self); @@ -128,8 +129,8 @@ static void rtc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t /* MicroPython bindings for machine API */ const nrfx_rtc_config_t machine_rtc_config = { - .prescaler = RTC_FREQ_TO_PRESCALER(RTC_FREQUENCY), - .reliable = 0, + .prescaler = RTC_FREQ_TO_PRESCALER(RTC_FREQUENCY), + .reliable = 0, .tick_latency = 0, // ignored when reliable == 0 #ifdef NRF51 .interrupt_priority = 3, @@ -161,7 +162,7 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s #endif // const and non-const part of the RTC object. - const machine_rtc_obj_t * self = &machine_rtc_obj[rtc_id]; + const machine_rtc_obj_t *self = &machine_rtc_obj[rtc_id]; machine_rtc_config_t *config = self->config; if (args[ARG_callback].u_obj == mp_const_none) { @@ -182,9 +183,7 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s } // Start the low-frequency clock (if it hasn't been started already) - if (!nrf_clock_lf_is_running(NRF_CLOCK)) { - nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART); - } + mp_nrf_start_lfclk(); // Make sure it's uninitialized. nrfx_rtc_uninit(self->p_rtc); @@ -202,7 +201,7 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s /// in the configured frequency has been reached. /// static mp_obj_t machine_rtc_start(mp_obj_t self_in) { - machine_rtc_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_rtc_obj_t *self = MP_OBJ_TO_PTR(self_in); nrfx_rtc_enable(self->p_rtc); @@ -214,7 +213,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(machine_rtc_start_obj, machine_rtc_start); /// Stop the RTCounter. /// static mp_obj_t machine_rtc_stop(mp_obj_t self_in) { - machine_rtc_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_rtc_obj_t *self = MP_OBJ_TO_PTR(self_in); nrfx_rtc_disable(self->p_rtc); @@ -227,7 +226,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(machine_rtc_stop_obj, machine_rtc_stop); /// with the current prescaler (2^24 / 8 = 2097152 seconds). /// static mp_obj_t machine_rtc_counter(mp_obj_t self_in) { - machine_rtc_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_rtc_obj_t *self = MP_OBJ_TO_PTR(self_in); uint32_t counter = nrfx_rtc_counter_get(self->p_rtc); @@ -239,7 +238,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(machine_rtc_counter_obj, machine_rtc_counter); /// Free resources associated with this RTC. /// static mp_obj_t machine_rtc_deinit(mp_obj_t self_in) { - machine_rtc_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_rtc_obj_t *self = MP_OBJ_TO_PTR(self_in); nrfx_rtc_uninit(self->p_rtc); diff --git a/ports/nrf/modules/machine/soft_pwm.c b/ports/nrf/modules/machine/soft_pwm.c index c61e1f86baa3..2fa336265898 100644 --- a/ports/nrf/modules/machine/soft_pwm.c +++ b/ports/nrf/modules/machine/soft_pwm.c @@ -102,7 +102,7 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args mp_raise_ValueError(MP_ERROR_TEXT("Pin number >31")); } - machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type);; + machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); self->defer_start = false; self->pwm_pin = pwm_pin; self->duty_mode = DUTY_NOT_SET; @@ -197,7 +197,7 @@ static void machine_soft_pwm_start(machine_pwm_obj_t *self) { duty_width = self->duty * DUTY_FULL_SCALE / 100; } else if (self->duty_mode == DUTY_U16) { duty_width = self->duty * DUTY_FULL_SCALE / 65536; - }if (self->duty_mode == DUTY_NS) { + } else if (self->duty_mode == DUTY_NS) { duty_width = (uint64_t)self->duty * self->freq * DUTY_FULL_SCALE / 1000000000ULL; } pwm_set_duty_cycle(self->pwm_pin, duty_width); diff --git a/ports/nrf/modules/machine/spi.c b/ports/nrf/modules/machine/spi.c index 4baee39d01dc..b00a5706c66c 100644 --- a/ports/nrf/modules/machine/spi.c +++ b/ports/nrf/modules/machine/spi.c @@ -98,20 +98,20 @@ #endif // NRFX_SPIM_ENABLED typedef struct _machine_hard_spi_obj_t { - mp_obj_base_t base; - const nrfx_spi_t * p_spi; // Driver instance - nrfx_spi_config_t * p_config; // pointer to volatile part + mp_obj_base_t base; + const nrfx_spi_t *p_spi; // Driver instance + nrfx_spi_config_t *p_config; // pointer to volatile part } machine_hard_spi_obj_t; static const nrfx_spi_t machine_spi_instances[] = { NRFX_SPI_INSTANCE(0), NRFX_SPI_INSTANCE(1), -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) NRFX_SPI_INSTANCE(2), -#if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED + #if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED NRFX_SPI_INSTANCE(3), -#endif // NRF52840_XXAA && NRFX_SPIM_ENABLED -#endif // NRF52_SERIES + #endif // NRF52840_XXAA && NRFX_SPIM_ENABLED + #endif // NRF52_SERIES }; static nrfx_spi_config_t configs[MP_ARRAY_SIZE(machine_spi_instances)]; @@ -119,12 +119,12 @@ static nrfx_spi_config_t configs[MP_ARRAY_SIZE(machine_spi_instances)]; static const machine_hard_spi_obj_t machine_hard_spi_obj[] = { {{&machine_spi_type}, .p_spi = &machine_spi_instances[0], .p_config = &configs[0]}, {{&machine_spi_type}, .p_spi = &machine_spi_instances[1], .p_config = &configs[1]}, -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) {{&machine_spi_type}, .p_spi = &machine_spi_instances[2], .p_config = &configs[2]}, -#if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED + #if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED {{&machine_spi_type}, .p_spi = &machine_spi_instances[3], .p_config = &configs[3]}, -#endif // NRF52840_XXAA && NRFX_SPIM_ENABLED -#endif // NRF52_SERIES + #endif // NRF52840_XXAA && NRFX_SPIM_ENABLED + #endif // NRF52_SERIES }; void spi_init0(void) { @@ -151,12 +151,12 @@ static int spi_find(mp_obj_t id) { } } -void spi_transfer(const machine_hard_spi_obj_t * self, size_t len, const void * src, void * dest) { +void spi_transfer(const machine_hard_spi_obj_t *self, size_t len, const void *src, void *dest) { nrfx_spi_xfer_desc_t xfer_desc = { .p_tx_buffer = src, - .tx_length = len, + .tx_length = len, .p_rx_buffer = dest, - .rx_length = len + .rx_length = len }; nrfx_spi_xfer(self->p_spi, &xfer_desc, 0); @@ -220,11 +220,11 @@ static mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_ar && args[ARG_NEW_mosi].u_obj != MP_OBJ_NULL && args[ARG_NEW_miso].u_obj != MP_OBJ_NULL) { - self->p_config->sck_pin = mp_hal_get_pin_obj(args[ARG_NEW_sck].u_obj)->pin; + self->p_config->sck_pin = mp_hal_get_pin_obj(args[ARG_NEW_sck].u_obj)->pin; self->p_config->mosi_pin = mp_hal_get_pin_obj(args[ARG_NEW_mosi].u_obj)->pin; self->p_config->miso_pin = mp_hal_get_pin_obj(args[ARG_NEW_miso].u_obj)->pin; } else { - self->p_config->sck_pin = MICROPY_HW_SPI0_SCK; + self->p_config->sck_pin = MICROPY_HW_SPI0_SCK; self->p_config->mosi_pin = MICROPY_HW_SPI0_MOSI; self->p_config->miso_pin = MICROPY_HW_SPI0_MISO; } @@ -232,11 +232,11 @@ static mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_ar // Manually trigger slave select from upper layer. self->p_config->ss_pin = NRFX_SPI_PIN_NOT_USED; -#ifdef NRF51 + #ifdef NRF51 self->p_config->irq_priority = 3; -#else + #else self->p_config->irq_priority = 6; -#endif + #endif machine_hard_spi_init_helper(self, &args[1]); // Skip instance id param. @@ -260,18 +260,18 @@ static void machine_hard_spi_init_helper(const machine_hard_spi_obj_t *self, mp_ self->p_config->frequency = NRF_SPI_FREQ_4M; } else if (baudrate <= 8000000) { self->p_config->frequency = NRF_SPI_FREQ_8M; -#if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED + #if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED } else if (baudrate <= 16000000) { self->p_config->frequency = NRF_SPIM_FREQ_16M; } else if (baudrate <= 32000000) { self->p_config->frequency = NRF_SPIM_FREQ_32M; -#endif // NRF52840_XXAA && NRFX_SPIM_ENABLED + #endif // NRF52840_XXAA && NRFX_SPIM_ENABLED } else { // Default self->p_config->frequency = NRF_SPI_FREQ_1M; } - // Active high if (args[ARG_INIT_polarity].u_int == 0) { + // Active high if (args[ARG_INIT_phase].u_int == 0) { // First clock edge self->p_config->mode = NRF_SPI_MODE_0; @@ -279,8 +279,8 @@ static void machine_hard_spi_init_helper(const machine_hard_spi_obj_t *self, mp_ // Second clock edge self->p_config->mode = NRF_SPI_MODE_1; } - // Active low } else { + // Active low if (args[ARG_INIT_phase].u_int == 0) { // First clock edge self->p_config->mode = NRF_SPI_MODE_2; @@ -290,7 +290,7 @@ static void machine_hard_spi_init_helper(const machine_hard_spi_obj_t *self, mp_ } } - self->p_config->orc = 0xFF; // Overrun character + self->p_config->orc = 0xFF; // Overrun character self->p_config->bit_order = (args[ARG_INIT_firstbit].u_int == 0) ? NRF_SPI_BIT_ORDER_MSB_FIRST : NRF_SPI_BIT_ORDER_LSB_FIRST; // Set context to this instance of SPI @@ -327,7 +327,7 @@ static void machine_hard_spi_deinit(mp_obj_base_t *self_in) { } static void machine_hard_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) { - const machine_hard_spi_obj_t *self = (machine_hard_spi_obj_t*)self_in; + const machine_hard_spi_obj_t *self = (machine_hard_spi_obj_t *)self_in; spi_transfer(self, len, src, dest); } diff --git a/ports/nrf/modules/machine/spi.h b/ports/nrf/modules/machine/spi.h index 1a1998d25aef..602829869fd2 100644 --- a/ports/nrf/modules/machine/spi.h +++ b/ports/nrf/modules/machine/spi.h @@ -29,7 +29,7 @@ typedef struct _machine_hard_spi_obj_t machine_hard_spi_obj_t; void spi_init0(void); -void spi_transfer(const machine_hard_spi_obj_t * self, - size_t len, - const void * src, - void * dest); +void spi_transfer(const machine_hard_spi_obj_t *self, + size_t len, + const void *src, + void *dest); diff --git a/ports/nrf/modules/machine/temp.c b/ports/nrf/modules/machine/temp.c index 0d206fee47a6..aa20087c6cd1 100644 --- a/ports/nrf/modules/machine/temp.c +++ b/ports/nrf/modules/machine/temp.c @@ -91,13 +91,13 @@ int32_t temp_read(void) { /// Get temperature. static mp_obj_t machine_temp_read(mp_uint_t n_args, const mp_obj_t *args) { -#if BLUETOOTH_SD + #if BLUETOOTH_SD if (BLUETOOTH_STACK_ENABLED() == 1) { int32_t temp; (void)sd_temp_get(&temp); return MP_OBJ_NEW_SMALL_INT(temp / 4); // resolution of 0.25 degree celsius } -#endif // BLUETOOTH_SD + #endif // BLUETOOTH_SD return MP_OBJ_NEW_SMALL_INT(temp_read()); } diff --git a/ports/nrf/modules/machine/timer.c b/ports/nrf/modules/machine/timer.c index 3c2a039b84df..42a40ad2f26a 100644 --- a/ports/nrf/modules/machine/timer.c +++ b/ports/nrf/modules/machine/timer.c @@ -37,32 +37,32 @@ enum { }; typedef struct _machine_timer_obj_t { - mp_obj_base_t base; - nrfx_timer_t p_instance; + mp_obj_base_t base; + nrfx_timer_t p_instance; } machine_timer_obj_t; static mp_obj_t machine_timer_callbacks[] = { NULL, NULL, NULL, -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) NULL, NULL, -#endif + #endif }; static const machine_timer_obj_t machine_timer_obj[] = { {{&machine_timer_type}, NRFX_TIMER_INSTANCE(0)}, -#if MICROPY_PY_MACHINE_SOFT_PWM + #if MICROPY_PY_MACHINE_SOFT_PWM { }, -#else + #else {{&machine_timer_type}, NRFX_TIMER_INSTANCE(1)}, -#endif + #endif {{&machine_timer_type}, NRFX_TIMER_INSTANCE(2)}, -#if defined(NRF52_SERIES) + #if defined(NRF52_SERIES) {{&machine_timer_type}, NRFX_TIMER_INSTANCE(3)}, {{&machine_timer_type}, NRFX_TIMER_INSTANCE(4)}, -#endif + #endif }; void timer_init0(void) { @@ -112,19 +112,19 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, // get static peripheral object int timer_id = timer_find(args[ARG_id].u_obj); -#if BLUETOOTH_SD + #if BLUETOOTH_SD if (timer_id == 0) { mp_raise_ValueError(MP_ERROR_TEXT("Timer reserved by Bluetooth LE stack")); } -#endif + #endif -#if MICROPY_PY_MACHINE_SOFT_PWM + #if MICROPY_PY_MACHINE_SOFT_PWM if (timer_id == 1) { mp_raise_ValueError(MP_ERROR_TEXT("Timer reserved by ticker driver")); } -#endif + #endif - machine_timer_obj_t *self = (machine_timer_obj_t*)&machine_timer_obj[timer_id]; + machine_timer_obj_t *self = (machine_timer_obj_t *)&machine_timer_obj[timer_id]; if (mp_obj_is_fun(args[ARG_callback].u_obj)) { machine_timer_callbacks[timer_id] = args[ARG_callback].u_obj; @@ -163,11 +163,11 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, ((args[ARG_mode].u_int == TIMER_MODE_ONESHOT) ? NRF_TIMER_SHORT_COMPARE0_STOP_MASK : 0); bool enable_interrupts = true; nrfx_timer_extended_compare( - &self->p_instance, - NRF_TIMER_CC_CHANNEL0, - args[ARG_period].u_int, - short_mask, - enable_interrupts); + &self->p_instance, + NRF_TIMER_CC_CHANNEL0, + args[ARG_period].u_int, + short_mask, + enable_interrupts); return MP_OBJ_FROM_PTR(self); } @@ -176,7 +176,7 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, /// Return counter value, which is currently in us. /// static mp_obj_t machine_timer_period(mp_obj_t self_in) { - machine_timer_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_timer_obj_t *self = MP_OBJ_TO_PTR(self_in); uint32_t period = nrfx_timer_capture(&self->p_instance, NRF_TIMER_CC_CHANNEL1); @@ -188,7 +188,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_period_obj, machine_timer_period) /// Start the timer. /// static mp_obj_t machine_timer_start(mp_obj_t self_in) { - machine_timer_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_timer_obj_t *self = MP_OBJ_TO_PTR(self_in); nrfx_timer_enable(&self->p_instance); @@ -200,7 +200,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_start_obj, machine_timer_start); /// Stop the timer. /// static mp_obj_t machine_timer_stop(mp_obj_t self_in) { - machine_timer_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_timer_obj_t *self = MP_OBJ_TO_PTR(self_in); nrfx_timer_disable(&self->p_instance); @@ -212,7 +212,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_stop_obj, machine_timer_stop); /// Free resources associated with the timer. /// static mp_obj_t machine_timer_deinit(mp_obj_t self_in) { - machine_timer_obj_t * self = MP_OBJ_TO_PTR(self_in); + machine_timer_obj_t *self = MP_OBJ_TO_PTR(self_in); nrfx_timer_uninit(&self->p_instance); diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 6da797df1f55..de26fa1b1278 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -88,8 +88,8 @@ typedef struct _machine_uart_buf_t { #endif typedef struct _machine_uart_obj_t { - mp_obj_base_t base; - const nrfx_uart_t * p_uart; // Driver instance + mp_obj_base_t base; + const nrfx_uart_t *p_uart; // Driver instance machine_uart_buf_t buf; uint16_t timeout; // timeout waiting for first char (in ms) uint16_t timeout_char; // timeout waiting between chars (in ms) @@ -206,19 +206,17 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg nrfx_uart_config_t config; // flow control -#if MICROPY_HW_UART1_HWFC + #if MICROPY_HW_UART1_HWFC config.hal_cfg.hwfc = NRF_UART_HWFC_ENABLED; -#else + #else config.hal_cfg.hwfc = NRF_UART_HWFC_DISABLED; -#endif + #endif config.hal_cfg.parity = NRF_UART_PARITY_EXCLUDED; -#if (BLUETOOTH_SD == 100) - config.interrupt_priority = 3; -#else - config.interrupt_priority = 6; -#endif + // Higher priority than pin interrupts, otherwise printing exceptions from + // interrupt handlers gets stuck. + config.interrupt_priority = NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY - 1; // These baudrates are not supported, it seems. if (args[ARG_baudrate].u_int < 1200 || args[ARG_baudrate].u_int > 1000000) { @@ -239,10 +237,10 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg config.pseltxd = MICROPY_HW_UART1_TX; config.pselrxd = MICROPY_HW_UART1_RX; -#if MICROPY_HW_UART1_HWFC + #if MICROPY_HW_UART1_HWFC config.pselrts = MICROPY_HW_UART1_RTS; config.pselcts = MICROPY_HW_UART1_CTS; -#endif + #endif self->timeout = args[ARG_timeout].u_int; self->timeout_char = args[ARG_timeout_char].u_int; @@ -259,9 +257,9 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg nrfx_uart_init(self->p_uart, &config, uart_event_handler); nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); -#if NRFX_UART_ENABLED + #if NRFX_UART_ENABLED nrfx_uart_rx_enable(self->p_uart); -#endif + #endif return MP_OBJ_FROM_PTR(self); } diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 30625382e43c..37fbdf1eb3b2 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -174,6 +174,7 @@ #define MICROPY_PY_TIME (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/nrf/modules/machine/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_PULSE (0) diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 2a1a4cb13ba3..06c6ba5cc2e7 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -40,6 +40,36 @@ #include "nrf_clock.h" #endif +#if !defined(USE_WORKAROUND_FOR_ANOMALY_132) && \ + (defined(NRF52832_XXAA) || defined(NRF52832_XXAB)) +// ANOMALY 132 - LFCLK needs to avoid frame from 66us to 138us after LFCLK stop. +#define USE_WORKAROUND_FOR_ANOMALY_132 1 +#endif + +#if USE_WORKAROUND_FOR_ANOMALY_132 +#include "soc/nrfx_coredep.h" +#endif + +void mp_nrf_start_lfclk(void) { + if (!nrf_clock_lf_start_task_status_get(NRF_CLOCK)) { + // Check if the clock was recently stopped but is still running. + #if USE_WORKAROUND_FOR_ANOMALY_132 + bool was_running = nrf_clock_lf_is_running(NRF_CLOCK); + // If so, wait for it to stop. This ensures that the delay for anomaly 132 workaround does + // not land us in the middle of the forbidden interval. + while (nrf_clock_lf_is_running(NRF_CLOCK)) { + } + // If the clock just stopped, we can start it again right away as we are certainly before + // the forbidden 66-138us interval. Otherwise, apply a delay of 138us to make sure we are + // after the interval. + if (!was_running) { + nrfx_coredep_delay_us(138); + } + #endif + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART); + } +} + #if MICROPY_PY_TIME_TICKS // Use RTC1 for time ticks generation (ms and us) with 32kHz tick resolution @@ -99,9 +129,7 @@ static void rtc_irq_time(nrfx_rtc_int_type_t event) { void rtc1_init_time_ticks(void) { // Start the low-frequency clock (if it hasn't been started already) - if (!nrf_clock_lf_is_running(NRF_CLOCK)) { - nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART); - } + mp_nrf_start_lfclk(); // Uninitialize first, then set overflow IRQ and first CC event nrfx_rtc_uninit(&rtc1); nrfx_rtc_init(&rtc1, &rtc_config_time_ticks, rtc_irq_time); diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 1ba4b6e70f08..7efe05a15fc8 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -54,6 +54,8 @@ void mp_hal_delay_us(mp_uint_t us); const char *nrfx_error_code_lookup(uint32_t err_code); +void mp_nrf_start_lfclk(void); + #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) diff --git a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/manifest.py b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/manifest.py index b3c6b02e5ba0..7b35b54c7a90 100644 --- a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/manifest.py +++ b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/manifest.py @@ -10,3 +10,9 @@ # Bluetooth require("aioble") + +# Register external library +add_library("arduino-lib", "$(ARDUINO_LIB_DIR)") + +# CMWX1 Lora module. +require("cmwx1", library="arduino-lib") diff --git a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk index de9902c16cf7..9bb336d65e9d 100644 --- a/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk +++ b/ports/renesas-ra/boards/ARDUINO_PORTENTA_C33/mpconfigboard.mk @@ -18,5 +18,8 @@ MICROPY_SSL_MBEDTLS = 1 MICROPY_PY_NETWORK = 1 MICROPY_PY_NETWORK_ESP_HOSTED = 1 +ARDUINO_LIB_DIR = lib/arduino-lib +GIT_SUBMODULES += $(ARDUINO_LIB_DIR) FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py +MICROPY_MANIFEST_ARDUINO_LIB_DIR = $(TOP)/$(ARDUINO_LIB_DIR) MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index ebd055f2ee9b..52effd64f929 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -131,6 +131,7 @@ #ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/renesas-ra/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 392fed29b1ff..fcc435b7b9f3 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -108,6 +108,7 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/tinyusb/mp_cdc_common.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ) set(MICROPY_SOURCE_DRIVERS @@ -146,6 +147,7 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_DIR}/shared/readline/readline.c ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ${MICROPY_PORT_DIR}/machine_adc.c ${MICROPY_PORT_DIR}/machine_i2c.c ${MICROPY_PORT_DIR}/machine_pin.c @@ -453,6 +455,15 @@ target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--wrap=dcd_event_handler ) +# Apply optimisations to performance-critical source code. +set_source_files_properties( + ${MICROPY_PY_DIR}/map.c + ${MICROPY_PY_DIR}/mpz.c + ${MICROPY_PY_DIR}/vm.c + PROPERTIES + COMPILE_OPTIONS "-O2" +) + set_source_files_properties( ${PICO_SDK_PATH}/src/rp2_common/pico_double/double_math.c ${PICO_SDK_PATH}/src/rp2_common/pico_float/float_math.c diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 70a67066fc9d..40374faff92e 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -221,6 +221,10 @@ int main(int argc, char **argv) { mp_thread_deinit(); #endif soft_timer_deinit(); + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif + gc_sweep_all(); mp_deinit(); } diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 8f073002e863..a29692d0be07 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -52,6 +52,10 @@ #ifndef MICROPY_HW_USB_MSC #define MICROPY_HW_USB_MSC (0) #endif + +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +#endif #endif #ifndef MICROPY_CONFIG_ROM_LEVEL @@ -115,6 +119,7 @@ #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (rosc_random_u32()) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/rp2/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 5a7cb9916a34..b678cd9828c6 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -136,6 +136,7 @@ SHARED_SRC_C += \ shared/tinyusb/mp_cdc_common.c \ shared/tinyusb/mp_usbd.c \ shared/tinyusb/mp_usbd_descriptor.c \ + shared/tinyusb/mp_usbd_runtime.c \ ASF4_SRC_C += $(addprefix lib/asf4/$(MCU_SERIES_LOWER)/,\ hal/src/hal_atomic.c \ diff --git a/ports/samd/README.md b/ports/samd/README.md index 49e967102ecd..8a7f121b66d3 100644 --- a/ports/samd/README.md +++ b/ports/samd/README.md @@ -23,7 +23,7 @@ bytecode. The 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/stm32/ directory. +All other commands below should be executed from the ports/samd/ directory. An ARM compiler is required for the build, along with the associated binary utilities. The default compiler is `arm-none-eabi-gcc`, which is available for diff --git a/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h index d3a6ba2d8687..5732a20e3adc 100644 --- a/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_TRINKET_M0/mpconfigboard.h @@ -1,2 +1,4 @@ #define MICROPY_HW_BOARD_NAME "Trinket M0" #define MICROPY_HW_MCU_NAME "SAMD21E18A" + +#define MICROPY_HW_DFLL_USB_SYNC (1) diff --git a/ports/samd/boards/MINISAM_M4/mpconfigboard.h b/ports/samd/boards/MINISAM_M4/mpconfigboard.h index 2dc403bad60f..87acf301e36b 100644 --- a/ports/samd/boards/MINISAM_M4/mpconfigboard.h +++ b/ports/samd/boards/MINISAM_M4/mpconfigboard.h @@ -1,4 +1,6 @@ #define MICROPY_HW_BOARD_NAME "Mini SAM M4" #define MICROPY_HW_MCU_NAME "SAMD51G19A" +#define MICROPY_HW_DFLL_USB_SYNC (1) + #define MICROPY_HW_QSPIFLASH GD25Q16C diff --git a/ports/samd/clock_config.h b/ports/samd/clock_config.h index 35b7e532814b..b045299396b1 100644 --- a/ports/samd/clock_config.h +++ b/ports/samd/clock_config.h @@ -30,5 +30,5 @@ void init_clocks(uint32_t cpu_freq); void set_cpu_freq(uint32_t cpu_freq); uint32_t get_cpu_freq(void); uint32_t get_peripheral_freq(void); -void check_usb_recovery_mode(void); +void check_usb_clock_recovery_mode(void); void enable_sercom_clock(int id); diff --git a/ports/samd/main.c b/ports/samd/main.c index f051e961ffa2..2bbaf63e6e4c 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -34,6 +34,7 @@ #include "shared/runtime/pyexec.h" #include "shared/runtime/softtimer.h" #include "shared/tinyusb/mp_usbd.h" +#include "clock_config.h" extern uint8_t _sstack, _estack, _sheap, _eheap; extern void adc_deinit_all(void); @@ -59,6 +60,7 @@ void samd_main(void) { int ret = pyexec_file_if_exists("boot.py"); mp_usbd_init(); + check_usb_clock_recovery_mode(); if (ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; @@ -93,6 +95,9 @@ void samd_main(void) { pwm_deinit_all(); #endif soft_timer_deinit(); + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif gc_sweep_all(); #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART sercom_deinit_all(); diff --git a/ports/samd/mcu/samd21/clock_config.c b/ports/samd/mcu/samd21/clock_config.c index d84ee526767c..033d0f7c1b1e 100644 --- a/ports/samd/mcu/samd21/clock_config.c +++ b/ports/samd/mcu/samd21/clock_config.c @@ -111,8 +111,29 @@ void set_cpu_freq(uint32_t cpu_freq_arg) { SysTick_Config(cpu_freq / 1000); } -void check_usb_recovery_mode(void) { - #if !MICROPY_HW_XOSC32K +static void sync_dfll48_with_xosc32kulp(void) { + SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE; + while (!SYSCTRL->PCLKSR.bit.DFLLRDY) { + } + // Connect GCLK4 to the DFLL input. + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN_GCLK4 | GCLK_CLKCTRL_ID_DFLL48 | GCLK_CLKCTRL_CLKEN; + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Set the multiplication values. The offset of 16384 to the freq is for rounding. + SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_MUL((CPU_FREQ + 16384) / 32768) | + SYSCTRL_DFLLMUL_FSTEP(1) | SYSCTRL_DFLLMUL_CSTEP(1); + while (SYSCTRL->PCLKSR.bit.DFLLRDY == 0) { + } + // Start the DFLL and wait for the PLL lock. We just wait for the fine lock, since + // coarse adjusting is bypassed. + SYSCTRL->DFLLCTRL.reg |= SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_WAITLOCK | SYSCTRL_DFLLCTRL_STABLE | + SYSCTRL_DFLLCTRL_BPLCKC | SYSCTRL_DFLLCTRL_ENABLE; + while (!SYSCTRL->PCLKSR.bit.DFLLLCKF) { + } +} + +void check_usb_clock_recovery_mode(void) { + #if MICROPY_HW_DFLL_USB_SYNC // Check USB status for up to 1 second. If not connected, // switch DFLL48M back to open loop for (int i = 0; i < 100; i++) { @@ -121,10 +142,9 @@ void check_usb_recovery_mode(void) { } mp_hal_delay_ms(10); } - // Set/keep the open loop mode of the device. - SYSCTRL->DFLLVAL.reg = dfll48m_calibration; - SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_CCDIS | SYSCTRL_DFLLCTRL_ENABLE; - #endif // MICROPY_HW_XOSC32K + // No USB sync. Use XOSC32KULP as clock reference for DFLL48M + sync_dfll48_with_xosc32kulp(); + #endif } // Purpose of the #defines for the clock configuration. @@ -178,12 +198,12 @@ void init_clocks(uint32_t cpu_freq) { // GCLK1: 32kHz, source: XOSC32K or OSCULP32K, usage: FDPLL96M reference // GCLK2: 1-48MHz, source: DFLL48M, usage: Peripherals // GCLK3: 2Mhz, source: DFLL48M, usage: us-counter (TC4/TC5) - // GCLK4: 32kHz, source: XOSC32K, if crystal present, usage: DFLL48M reference + // GCLK4: 32kHz, source: XOSC32K or OSCULP32K, usage: DFLL48M reference // GCLK5: 48MHz, source: DFLL48M, usage: USB // GCLK8: 1kHz, source: XOSC32K or OSCULP32K, usage: WDT and RTC // DFLL48M: Reference sources: - // - in closed loop mode: either XOSC32K or OSCULP32K or USB clock - // from GCLK4. + // - in closed loop mode: either XOSC32K or OSCULP32K from GCLK4 + // or USB clock. // - in open loop mode: None // FDPLL96M: Reference source GCLK1 // Used for the CPU clock for freq >= 48Mhz @@ -256,6 +276,17 @@ void init_clocks(uint32_t cpu_freq) { #else // MICROPY_HW_XOSC32K + // Connect the GCLK1 to the XOSC32KULP + GCLK->GENDIV.reg = GCLK_GENDIV_ID(1) | GCLK_GENDIV_DIV(1); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(1); + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Connect the GCLK4 to the XOSC32KULP + GCLK->GENDIV.reg = GCLK_GENDIV_ID(4) | GCLK_GENDIV_DIV(1); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(4); + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Enable DFLL48M SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE; while (!SYSCTRL->PCLKSR.bit.DFLLRDY) { @@ -263,35 +294,30 @@ void init_clocks(uint32_t cpu_freq) { uint32_t coarse = (*((uint32_t *)FUSES_DFLL48M_COARSE_CAL_ADDR) & FUSES_DFLL48M_COARSE_CAL_Msk) >> FUSES_DFLL48M_COARSE_CAL_Pos; + uint32_t fine = (*((uint32_t *)FUSES_DFLL48M_FINE_CAL_ADDR) & FUSES_DFLL48M_FINE_CAL_Msk) + >> FUSES_DFLL48M_COARSE_CAL_Pos; if (coarse == 0x3f) { coarse = 0x1f; } - SYSCTRL->DFLLVAL.reg = SYSCTRL_DFLLVAL_COARSE(coarse) | SYSCTRL_DFLLVAL_FINE(511); + SYSCTRL->DFLLVAL.reg = SYSCTRL_DFLLVAL_COARSE(coarse) | SYSCTRL_DFLLVAL_FINE(fine); + dfll48m_calibration = SYSCTRL_DFLLVAL_COARSE(coarse) | SYSCTRL_DFLLVAL_FINE(fine); #if MICROPY_HW_DFLL_USB_SYNC - // Configure the DFLL48M for USB clock recovery. - // Will have to switch back if no USB - SYSCTRL->DFLLSYNC.bit.READREQ = 1; - dfll48m_calibration = SYSCTRL->DFLLVAL.reg; // Set the Multiplication factor. SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_CSTEP(1) | SYSCTRL_DFLLMUL_FSTEP(1) | SYSCTRL_DFLLMUL_MUL(48000); // Set the mode to closed loop USB Recovery mode SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_USBCRM | SYSCTRL_DFLLCTRL_CCDIS | SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_ENABLE; - #else - // Set/keep the open loop mode of the device. - SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_CCDIS | SYSCTRL_DFLLCTRL_ENABLE; - #endif - while (!SYSCTRL->PCLKSR.bit.DFLLRDY) { } - // Connect the GCLK1 to the XOSC32KULP - GCLK->GENDIV.reg = GCLK_GENDIV_ID(1) | GCLK_GENDIV_DIV(1); - GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(1); - while (GCLK->STATUS.bit.SYNCBUSY) { - } + #else // MICROPY_HW_DFLL_USB_SYNC + + sync_dfll48_with_xosc32kulp(); + + #endif // MICROPY_HW_DFLL_USB_SYNC + // Set GCLK8 to 1 kHz. GCLK->GENDIV.reg = GCLK_GENDIV_ID(8) | GCLK_GENDIV_DIV(32); GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(8); diff --git a/ports/samd/mcu/samd51/clock_config.c b/ports/samd/mcu/samd51/clock_config.c index b30cc46f2459..77b6e4c632cd 100644 --- a/ports/samd/mcu/samd51/clock_config.c +++ b/ports/samd/mcu/samd51/clock_config.c @@ -115,8 +115,8 @@ void set_cpu_freq(uint32_t cpu_freq_arg) { SysTick_Config(cpu_freq / 1000); } -void check_usb_recovery_mode(void) { - #if !MICROPY_HW_XOSC32K +void check_usb_clock_recovery_mode(void) { + #if MICROPY_HW_DFLL_USB_SYNC // Check USB status for up to 1 second. If not connected, // switch DFLL48M back to open loop for (int i = 0; i < 100; i++) { @@ -144,7 +144,7 @@ void check_usb_recovery_mode(void) { OSCCTRL->DFLLCTRLB.reg = 0; while (OSCCTRL->DFLLSYNC.bit.DFLLCTRLB == 1) { } - #endif // MICROPY_HW_XOSC32K + #endif } // Purpose of the #defines for the clock configuration. diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 160442a4006b..0b47500bf7e7 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -63,8 +63,13 @@ #ifndef MICROPY_HW_USB_DESC_STR_MAX #define MICROPY_HW_USB_DESC_STR_MAX (32) #endif +// Support machine.USBDevice +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) #endif +#endif // MICROPY_HW_ENABLE_USBDEV + #define MICROPY_PY_SYS_PLATFORM "samd" // Extended modules @@ -73,6 +78,7 @@ #define MICROPY_PY_TIME_INCLUDEFILE "ports/samd/modtime.c" #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/samd/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) diff --git a/ports/samd/samd_qspiflash.c b/ports/samd/samd_qspiflash.c index 60aa434dcfda..d027a0495f3c 100644 --- a/ports/samd/samd_qspiflash.c +++ b/ports/samd/samd_qspiflash.c @@ -337,9 +337,13 @@ static mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args // The write in progress bit should be low. while (read_status() & 0x01) { } - // The suspended write/erase bit should be low. - while (read_status2() & 0x80) { + + if (!flash_device->single_status_byte) { + // The suspended write/erase bit should be low. + while (read_status2() & 0x80) { + } } + run_command(QSPI_CMD_ENABLE_RESET); run_command(QSPI_CMD_RESET); // Wait 30us for the reset diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index 5e6c5c4fc240..1c8d49d1573d 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -116,7 +116,6 @@ void samd_init(void) { init_clocks(get_cpu_freq()); init_us_counter(); usb_init(); - check_usb_recovery_mode(); #if defined(MCU_SAMD51) mp_hal_ticks_cpu_enable(); #endif diff --git a/ports/samd/samd_spiflash.c b/ports/samd/samd_spiflash.c index 1313a6fd9d0d..cd4f10640dbf 100644 --- a/ports/samd/samd_spiflash.c +++ b/ports/samd/samd_spiflash.c @@ -49,6 +49,9 @@ const uint8_t _COMMANDS_32BIT[] = {0x13, 0x12, 0x21}; // READ, PROGRAM_PAGE, ER #define COMMAND_READ_SFDP (0x5A) #define PAGE_SIZE (256) #define SECTOR_SIZE (4096) +#ifndef MICROPY_HW_SPIFLASH_BAUDRATE +#define MICROPY_HW_SPIFLASH_BAUDRATE (24000000) +#endif typedef struct _spiflash_obj_t { mp_obj_base_t base; @@ -136,7 +139,7 @@ static mp_obj_t spiflash_make_new(const mp_obj_type_t *type, size_t n_args, size mp_obj_t spi_args[] = { MP_OBJ_NEW_SMALL_INT(MICROPY_HW_SPIFLASH_ID), - MP_OBJ_NEW_SMALL_INT(24000000), // baudrate + MP_OBJ_NEW_SMALL_INT(MICROPY_HW_SPIFLASH_BAUDRATE), MP_OBJ_NEW_QSTR(MP_QSTR_mosi), MP_OBJ_NEW_QSTR(MP_QSTR_FLASH_MOSI), MP_OBJ_NEW_QSTR(MP_QSTR_miso), MP_OBJ_NEW_QSTR(MP_QSTR_FLASH_MISO), MP_OBJ_NEW_QSTR(MP_QSTR_sck), MP_OBJ_NEW_QSTR(MP_QSTR_FLASH_SCK), diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index d1694426d352..50ac48e5a18e 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -422,6 +422,17 @@ endif endif # MICROPY_PY_BLUETOOTH +# Add stm32-specific implementation of libmetal (and optionally OpenAMP's rproc). +# Note: libmetal code is generated via a pre-processor so ensure that runs first. +ifeq ($(MICROPY_PY_OPENAMP),1) +SRC_C += mpmetalport.c +$(BUILD)/mpmetalport.o: $(BUILD)/openamp/metal/config.h +ifeq ($(MICROPY_PY_OPENAMP_REMOTEPROC),1) +SRC_C += mpremoteprocport.c +$(BUILD)/mpremoteprocport.o: $(BUILD)/openamp/metal/config.h +endif +endif + # SRC_O should be placed first to work around this LTO bug with binutils <2.35: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83967 OBJ += $(addprefix $(BUILD)/, $(SRC_O)) diff --git a/ports/stm32/README.md b/ports/stm32/README.md index 15025c5ae15c..cd9e291396ef 100644 --- a/ports/stm32/README.md +++ b/ports/stm32/README.md @@ -2,9 +2,9 @@ MicroPython port to STM32 MCUs ============================== This directory contains the port of MicroPython to ST's line of STM32 -microcontrollers. Supported MCU series are: STM32F0, STM32F4, STM32F7, -STM32H7, STM32L0, STM32L4 and STM32WB. Parts of the code here utilise the -STM32Cube HAL library. +microcontrollers. Supported MCU series are: STM32F0, STM32F4, STM32F7, STM32G0, +STM32G4, STM32H5, STM32H7, STM32L0, STM32L1, STM32L4, STM32WL and STM32WB. +Parts of the code here utilise the STM32Cube HAL library. The officially supported boards are the line of pyboards: PYBv1.0 and PYBv1.1 (both with STM32F405), PYBLITEv1.0 (with STM32F411) and PYBD-SFx (with @@ -16,12 +16,6 @@ Other boards that are supported include ST Discovery and Nucleo boards. See the boards/ subdirectory, which contains the configuration files used to build each individual board. -The STM32H7 series has preliminary support: there is a working REPL via -USB and UART, as well as very basic peripheral support, but some things do -not work and none of the advanced features of the STM32H7 are yet supported, -such as the clock tree. At this point the STM32H7 should be considered as a -fast version of the STM32F7. - Build instructions ------------------ diff --git a/ports/stm32/boards/ARDUINO_GIGA/manifest.py b/ports/stm32/boards/ARDUINO_GIGA/manifest.py index b3c6b02e5ba0..b7b9a2b470d0 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/manifest.py +++ b/ports/stm32/boards/ARDUINO_GIGA/manifest.py @@ -10,3 +10,9 @@ # Bluetooth require("aioble") + +# Register external library +add_library("arduino-lib", "$(ARDUINO_LIB_DIR)") + +# RPC +require("msgpackrpc", library="arduino-lib") diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk index b927388c5577..f58c98af83cf 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk @@ -23,6 +23,11 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 +ARDUINO_LIB_DIR = lib/arduino-lib +GIT_SUBMODULES += $(ARDUINO_LIB_DIR) FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py +MICROPY_MANIFEST_ARDUINO_LIB_DIR = $(TOP)/$(ARDUINO_LIB_DIR) MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld index d5bd9598f982..793a76b97029 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,4 +44,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/manifest.py b/ports/stm32/boards/ARDUINO_NICLA_VISION/manifest.py index b3c6b02e5ba0..b7b9a2b470d0 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/manifest.py +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/manifest.py @@ -10,3 +10,9 @@ # Bluetooth require("aioble") + +# Register external library +add_library("arduino-lib", "$(ARDUINO_LIB_DIR)") + +# RPC +require("msgpackrpc", library="arduino-lib") diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk index 2c48c17f3805..0e8ff0e82d38 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk @@ -23,6 +23,11 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 +ARDUINO_LIB_DIR = lib/arduino-lib +GIT_SUBMODULES += $(ARDUINO_LIB_DIR) FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py +MICROPY_MANIFEST_ARDUINO_LIB_DIR = $(TOP)/$(ARDUINO_LIB_DIR) MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld index fcbfc94d4620..6d6ce279f299 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } _cm4_ram_start = ORIGIN(SRAM4); @@ -46,4 +46,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/manifest.py b/ports/stm32/boards/ARDUINO_PORTENTA_H7/manifest.py index b3c6b02e5ba0..eb53df0fbdeb 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/manifest.py +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/manifest.py @@ -10,3 +10,10 @@ # Bluetooth require("aioble") + +# Register external library +add_library("arduino-lib", "$(ARDUINO_LIB_DIR)") + +# RPC +require("cmwx1", library="arduino-lib") +require("msgpackrpc", library="arduino-lib") diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk index cf4d40e5fe9b..3b173b3acdb9 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk @@ -23,6 +23,11 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 +ARDUINO_LIB_DIR = lib/arduino-lib +GIT_SUBMODULES += $(ARDUINO_LIB_DIR) FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py +MICROPY_MANIFEST_ARDUINO_LIB_DIR = $(TOP)/$(ARDUINO_LIB_DIR) MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld index d5bd9598f982..793a76b97029 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,4 +44,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld diff --git a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py index 02398e81243f..8229004fbbd0 100644 --- a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py +++ b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py @@ -1,51 +1,61 @@ # Application firmware update function for LEGO_HUB_NO6. -# MIT license; Copyright (c) 2022 Damien P. George +# MIT license; Copyright (c) 2022-2024 Damien P. George from micropython import const -import struct, machine, fwupdate, spiflash, pyb +import random, struct, machine, fwupdate, spiflash, pyb _IOCTL_BLOCK_COUNT = const(4) _IOCTL_BLOCK_SIZE = const(5) -_SPIFLASH_UPDATE_KEY_ADDR = const(1020 * 1024) -_SPIFLASH_UPDATE_KEY_VALUE = const(0x12345678) - -_FILESYSTEM_ADDR = const(0x8000_0000 + 1024 * 1024) - -# Roundabout way to get actual filesystem size from config. -# This takes into account the 1M "reserved" section of the flash memory. -flash = pyb.Flash(start=0) -_FILESYSTEM_LEN = flash.ioctl(_IOCTL_BLOCK_COUNT, None) * flash.ioctl(_IOCTL_BLOCK_SIZE, None) +# Mboot addresses the external SPI flash at this location. +_MBOOT_SPIFLASH_BASE_ADDR = 0x80000000 + +# The raw filesystem is in the first 1MiB of external SPI flash, +# but skip the first and last flash sectors. +_RAW_FILESYSTEM_ADDR = const(4 * 1024) +_RAW_FILESYSTEM_LEN = const(1016 * 1024) + + +def _copy_file_to_raw_filesystem(filename, flash, block): + block_count = flash.ioctl(_IOCTL_BLOCK_COUNT, 0) + block_size = flash.ioctl(_IOCTL_BLOCK_SIZE, 0) + buf = bytearray(block_size) + with open(filename, "rb") as file: + while True: + n = file.readinto(buf) + if not n: + break + flash.writeblocks(block, buf) + block += 1 + if block >= block_count: + print("|", end="") + block = 0 + print(".", end="") + print() def update_app(filename): print(f"Updating application firmware from {filename}") - # Create the elements for the mboot filesystem-load operation. - elems = fwupdate.update_app_elements(filename, _FILESYSTEM_ADDR, _FILESYSTEM_LEN) - if not elems: - return - - # Create the update key. - key = struct.pack("start, self->len); +} + +static mp_obj_t spi_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_start, ARG_len }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + spi_flash_obj_t *self = mp_obj_malloc(spi_flash_obj_t, &spi_flash_type); + + mp_int_t start = args[ARG_start].u_int; + if (!(0 <= start && start < FLASH_SIZE && start % BLOCK_SIZE == 0)) { + mp_raise_ValueError(NULL); + } + + mp_int_t len = args[ARG_len].u_int; + if (len == -1) { + len = FLASH_SIZE - start; + } else if (!(0 < len && start + len <= FLASH_SIZE && len % BLOCK_SIZE == 0)) { + mp_raise_ValueError(NULL); + } + + self->start = start; + self->len = len; + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t spi_flash_readblocks(size_t n_args, const mp_obj_t *args) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t block_num = self->start / BLOCK_SIZE + mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); + int ret = spi_bdev_readblocks_raw(&spi_bdev, bufinfo.buf, block_num, 0, bufinfo.len); + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spi_flash_readblocks_obj, 3, 3, spi_flash_readblocks); + +static mp_obj_t spi_flash_writeblocks(size_t n_args, const mp_obj_t *args) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t block_num = self->start / BLOCK_SIZE + mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + int ret = spi_bdev_eraseblocks_raw(&spi_bdev, block_num, bufinfo.len); + if (ret == 0) { + ret = spi_bdev_writeblocks_raw(&spi_bdev, bufinfo.buf, block_num, 0, bufinfo.len); + } + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spi_flash_writeblocks_obj, 3, 3, spi_flash_writeblocks); + +static mp_obj_t spi_flash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + storage_init(); + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_DEINIT: + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_SYNC: + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: { + mp_int_t n = self->len / BLOCK_SIZE; + return MP_OBJ_NEW_SMALL_INT(n); + } + + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE); + + default: + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_ioctl_obj, spi_flash_ioctl); + +static const mp_rom_map_elem_t spi_flash_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&spi_flash_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&spi_flash_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&spi_flash_ioctl_obj) }, +}; +static MP_DEFINE_CONST_DICT(spi_flash_locals_dict, spi_flash_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + spi_flash_type, + MP_QSTR_SPIFlash, + MP_TYPE_FLAG_NONE, + make_new, spi_flash_make_new, + print, spi_flash_print, + locals_dict, &spi_flash_locals_dict + ); + +/******************************************************************************/ +// The `spiflash` module. + +static const mp_rom_map_elem_t spiflash_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_spiflash) }, + + { MP_ROM_QSTR(MP_QSTR_SPIFlash), MP_ROM_PTR(&spi_flash_type) }, +}; +static MP_DEFINE_CONST_DICT(spiflash_module_globals, spiflash_module_globals_table); + +const mp_obj_module_t spiflash_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&spiflash_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_spiflash, spiflash_module); + +#endif diff --git a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py b/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py deleted file mode 100644 index e483ace95084..000000000000 --- a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py +++ /dev/null @@ -1,90 +0,0 @@ -# MicroPython driver for SPI flash -# MIT license; Copyright (c) 2022 Damien P. George - -from micropython import const - -_PAGE_SIZE = const(256) # maximum bytes writable in one SPI transfer -_CMD_WRITE = const(0x02) -_CMD_READ = const(0x03) -_CMD_RDSR = const(0x05) -_CMD_WREN = const(0x06) -_CMD_WRITE_32 = const(0x12) -_CMD_READ_32 = const(0x13) -_CMD_SEC_ERASE = const(0x20) -_CMD_SEC_ERASE_32 = const(0x21) -_CMD_JEDEC_ID = const(0x9F) - - -class SPIFlash: - def __init__(self, spi, cs): - self.spi = spi - self.cs = cs - self.id = self._get_id() - # flash chip on Hub No. 6 uses 32-bit addressing - _32_bit = self.id == b"\xef\x40\x19" - self._READ = _CMD_READ_32 if _32_bit else _CMD_READ - self._WRITE = _CMD_WRITE_32 if _32_bit else _CMD_WRITE - self._ERASE = _CMD_SEC_ERASE_32 if _32_bit else _CMD_SEC_ERASE - - def _get_id(self): - self.cs(0) - self.spi.write(bytearray([_CMD_JEDEC_ID])) - buf = self.spi.read(3) - self.cs(1) - return buf - - def _wait_wel1(self): - # wait WEL=1 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if buf[0] & 2: - break - self.cs(1) - - def _wait_wip0(self): - # wait WIP=0 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if not (buf[0] & 1): - break - self.cs(1) - - def _flash_modify(self, cmd, addr, buf): - self.cs(0) - self.spi.write(bytearray([_CMD_WREN])) - self.cs(1) - self._wait_wel1() - self.cs(0) - self.spi.write(bytearray([cmd, addr >> 24, addr >> 16, addr >> 8, addr])) - if buf: - self.spi.write(buf) - self.cs(1) - self._wait_wip0() - - def erase_block(self, addr): - self._flash_modify(self._ERASE, addr, None) - - def readinto(self, addr, buf): - self.cs(0) - self.spi.write(bytearray([self._READ, addr >> 16, addr >> 8, addr])) - self.spi.readinto(buf) - self.cs(1) - - def write(self, addr, buf): - offset = addr & (_PAGE_SIZE - 1) - remain = len(buf) - buf = memoryview(buf) - buf_offset = 0 - while remain: - l = min(_PAGE_SIZE - offset, remain) - self._flash_modify(self._WRITE, addr, buf[buf_offset : buf_offset + l]) - remain -= l - addr += l - buf_offset += l - offset = 0 diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h index e3a204ec96a3..58e6d0a80485 100644 --- a/ports/stm32/irq.h +++ b/ports/stm32/irq.h @@ -174,6 +174,8 @@ static inline void restore_irq_pri(uint32_t state) { #define IRQ_PRI_SPI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0) +#define IRQ_PRI_HSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 10, 0) + // Interrupt priority for non-special timers. #define IRQ_PRI_TIMX NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 13, 0) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 802a884d87d7..90a62c024427 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -477,6 +477,7 @@ void stm32_main(uint32_t reset_mode) { memcpy(&buf[0], "PYBD", 4); mp_hal_get_mac_ascii(MP_HAL_MAC_WLAN0, 8, 4, (char *)&buf[4]); cyw43_wifi_ap_set_ssid(&cyw43_state, 8, buf); + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_WPA2_MIXED_PSK); cyw43_wifi_ap_set_password(&cyw43_state, 8, (const uint8_t *)"pybd0123"); } #endif diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 389f9f5d0f4a..07053b329390 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -120,6 +120,7 @@ SRC_C += \ ui.c \ vfs_fat.c \ vfs_lfs.c \ + vfs_raw.c \ drivers/bus/softspi.c \ drivers/bus/softqspi.c \ drivers/memory/spiflash.c \ diff --git a/ports/stm32/mboot/README.md b/ports/stm32/mboot/README.md index 73e32fcedcc9..221e3a7c3aca 100644 --- a/ports/stm32/mboot/README.md +++ b/ports/stm32/mboot/README.md @@ -71,13 +71,23 @@ How to use #define MBOOT_FSLOAD (1) and then enable one or more of the following depending on what filesystem - support is required in Mboot (note that the FAT driver is read-only and - quite compact, but littlefs supports both read and write so is rather - large): + support is required in Mboot: #define MBOOT_VFS_FAT (1) #define MBOOT_VFS_LFS1 (1) #define MBOOT_VFS_LFS2 (1) + #define MBOOT_VFS_RAW (1) + + Note that the FAT and LFS2 drivers are read-only and quite compact, but + LFS1 supports both read and write so is rather large. + + The raw filesystem type is enabled by default and is a flat section of + storage containing a single file without any metadata. The raw filesystem + can either be one regoin, or split over two separate, contiguous regions. + The latter is useful for wear levelling: given a chunk of flash, write the + firmware starting at a random block within that chunk and wrap around to + the beginning of the chunk when the end is reached. Then use a split + raw filesystem to inform mboot of this wrapping. 2. Build the board's main application firmware as usual. diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index abc3514ed146..83905eb49107 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -39,7 +39,7 @@ #if MBOOT_FSLOAD -#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) +#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2 || MBOOT_VFS_RAW) #error Must enable at least one VFS component #endif @@ -234,28 +234,51 @@ int fsload_process(void) { // End of elements. return -MBOOT_ERRNO_FSLOAD_NO_MOUNT; } + + // Extract element arguments based on the element length: + // - 10 bytes: mount_point fs_type uint32_t uint32_t + // - 14 bytes: mount_point fs_type uint32_t uint32_t uint32_t + // - 18 bytes: mount_point fs_type uint32_t uint32_t uint32_t uint32_t + // - 22 bytes: mount_point fs_type uint64_t uint64_t uint32_t + // - 34 bytes: mount_point fs_type uint64_t uint64_t uint64_t uint64_t mboot_addr_t base_addr; mboot_addr_t byte_len; - uint32_t block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; - if (elem[-1] == 10 || elem[-1] == 14) { + mboot_addr_t arg2 = 0; + mboot_addr_t arg3 = 0; + (void)arg2; + (void)arg3; + uint8_t elem_len = elem[-1]; + if (elem_len == 10 || elem_len == 14 || elem_len == 18) { // 32-bit base and length given, extract them. base_addr = get_le32(&elem[2]); byte_len = get_le32(&elem[6]); - if (elem[-1] == 14) { - // Block size given, extract it. - block_size = get_le32(&elem[10]); + if (elem_len >= 14) { + // Argument 2 given, extract it. + arg2 = get_le32(&elem[10]); + if (elem_len == 18) { + // Argument 3 given, extract it. + arg3 = get_le32(&elem[14]); + } } #if MBOOT_ADDRESS_SPACE_64BIT - } else if (elem[-1] == 22) { - // 64-bit base and length given, and block size, so extract them. + } else if (elem_len == 22 || elem_len == 34) { + // 64-bit base and length given, so extract them. base_addr = get_le64(&elem[2]); byte_len = get_le64(&elem[10]); - block_size = get_le32(&elem[18]); + if (elem_len == 22) { + // 32-bit argument 2 given, extract it. + arg2 = get_le32(&elem[18]); + } else { + // 64-bit argument 2 and 3 given, extract them. + arg2 = get_le64(&elem[18]); + arg3 = get_le64(&elem[26]); + } #endif } else { // Invalid MOUNT element. return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; } + if (elem[0] == mount_point) { int ret; union { @@ -268,27 +291,43 @@ int fsload_process(void) { #if MBOOT_VFS_LFS2 vfs_lfs2_context_t lfs2; #endif + #if MBOOT_VFS_RAW + vfs_raw_context_t raw; + #endif } ctx; const stream_methods_t *methods; #if MBOOT_VFS_FAT if (elem[1] == ELEM_MOUNT_FAT) { - (void)block_size; ret = vfs_fat_mount(&ctx.fat, base_addr, byte_len); methods = &vfs_fat_stream_methods; } else #endif #if MBOOT_VFS_LFS1 if (elem[1] == ELEM_MOUNT_LFS1) { + uint32_t block_size = arg2; + if (block_size == 0) { + block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; + } ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len, block_size); methods = &vfs_lfs1_stream_methods; } else #endif #if MBOOT_VFS_LFS2 if (elem[1] == ELEM_MOUNT_LFS2) { + uint32_t block_size = arg2; + if (block_size == 0) { + block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; + } ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len, block_size); methods = &vfs_lfs2_stream_methods; } else #endif + #if MBOOT_VFS_RAW + if (elem[1] == ELEM_MOUNT_RAW) { + ret = vfs_raw_mount(&ctx.raw, base_addr, byte_len, arg2, arg3); + methods = &vfs_raw_stream_methods; + } else + #endif { // Unknown filesystem type return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index 47ceb19ba5da..8578ff4fc903 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -9,6 +9,7 @@ VFS_FAT = 1 VFS_LFS1 = 2 VFS_LFS2 = 3 +VFS_RAW = 4 # Constants for creating mboot elements. _ELEM_TYPE_END = const(1) @@ -226,28 +227,45 @@ def _create_element(kind, body): def update_app_elements( - filename, fs_base, fs_len, fs_type=VFS_FAT, fs_blocksize=0, status_addr=None, addr_64bit=False + filename, + fs_base, + fs_len, + fs_type=VFS_FAT, + fs_blocksize=0, + status_addr=None, + addr_64bit=False, + *, + fs_base2=0, + fs_len2=0, ): - # Check firmware is of .dfu or .dfu.gz type - try: - with open(filename, "rb") as f: - hdr = deflate.DeflateIO(f, deflate.GZIP).read(6) - except Exception: - with open(filename, "rb") as f: - hdr = f.read(6) - if hdr != b"DfuSe\x01": - print("Firmware must be a .dfu(.gz) file.") - return () + if fs_type != VFS_RAW: + # Check firmware is of .dfu or .dfu.gz type + try: + with open(filename, "rb") as f: + hdr = deflate.DeflateIO(f, deflate.GZIP).read(6) + except Exception: + with open(filename, "rb") as f: + hdr = f.read(6) + if hdr != b"DfuSe\x01": + print("Firmware must be a .dfu(.gz) file.") + return () if fs_type in (VFS_LFS1, VFS_LFS2) and not fs_blocksize: raise Exception("littlefs requires fs_blocksize parameter") mount_point = 1 - mount_encoding = "seg0_base_addr = seg0_base_addr; + ctx->seg0_byte_len = seg0_byte_len; + ctx->seg1_base_addr = seg1_base_addr; + ctx->seg1_byte_len = seg1_byte_len; + return 0; +} + +static int vfs_raw_stream_open(void *stream_in, const char *fname) { + vfs_raw_context_t *stream = stream_in; + (void)fname; + stream->file_pos = 0; + return 0; +} + +static void vfs_raw_stream_close(void *stream_in) { + (void)stream_in; +} + +static int vfs_raw_stream_read(void *stream_in, uint8_t *buf, size_t len) { + vfs_raw_context_t *stream = stream_in; + size_t orig_len = len; + while (len) { + mboot_addr_t addr; + mboot_addr_t remain; + if (stream->file_pos < stream->seg0_byte_len) { + // Reading from segment 0. + mboot_addr_t seg0_pos = stream->file_pos; + addr = stream->seg0_base_addr + seg0_pos; + remain = stream->seg0_byte_len - seg0_pos; + } else { + // Reading from segment 1. + mboot_addr_t seg1_pos = stream->file_pos - stream->seg0_byte_len; + addr = stream->seg1_base_addr + seg1_pos; + remain = stream->seg1_byte_len - seg1_pos; + if (!remain) { + // At the end of segment 1. + break; + } + } + size_t l = MIN(len, remain); + hw_read(addr, l, buf); + stream->file_pos += l; + buf += l; + len -= l; + } + return orig_len - len; +} + +const stream_methods_t vfs_raw_stream_methods = { + vfs_raw_stream_open, + vfs_raw_stream_close, + vfs_raw_stream_read, +}; + +#endif // MBOOT_FSLOAD && MBOOT_VFS_RAW diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 300ad086bf47..9e1e24cf2382 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -111,6 +111,7 @@ #ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/stm32/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/stm32/mpmetalport.c b/ports/stm32/mpmetalport.c new file mode 100644 index 000000000000..72f5537ce574 --- /dev/null +++ b/ports/stm32/mpmetalport.c @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * libmetal stm32 port. + */ + +#include "py/mperrno.h" +#include "py/mphal.h" + +#include "mpu.h" + +#include "metal/sys.h" +#include "metal/utilities.h" +#include "metal/device.h" + +struct metal_state _metal; +static mp_sched_node_t rproc_notify_node; + +int metal_sys_init(const struct metal_init_params *params) { + metal_unused(params); + + // Clear HSEM pending IRQ. + HSEM_COMMON->ICR |= (uint32_t)__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID); + HAL_NVIC_ClearPendingIRQ(HSEM1_IRQn); + + // Enable and configure HSEM. + __HAL_RCC_HSEM_CLK_ENABLE(); + NVIC_SetPriority(HSEM1_IRQn, IRQ_PRI_HSEM); + HAL_NVIC_EnableIRQ(HSEM1_IRQn); + HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); + + #ifndef VIRTIO_USE_DCACHE + // If cache management is not enabled, configure the MPU to disable caching + // for the entire shared memory region. + uint32_t irq_state = mpu_config_start(); + mpu_config_region(MPU_REGION_OPENAMP, METAL_MPU_REGION_BASE, MPU_CONFIG_SHARED_UNCACHED(METAL_MPU_REGION_SIZE)); + mpu_config_end(irq_state); + #endif + + metal_bus_register(&metal_generic_bus); + return 0; +} + +void metal_sys_finish(void) { + HAL_NVIC_DisableIRQ(HSEM1_IRQn); + HAL_HSEM_DeactivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); + __HAL_RCC_HSEM_CLK_DISABLE(); + metal_bus_unregister(&metal_generic_bus); +} + +unsigned int sys_irq_save_disable(void) { + return disable_irq(); +} + +void sys_irq_restore_enable(unsigned int state) { + enable_irq(state); +} + +void *metal_machine_io_mem_map(void *va, metal_phys_addr_t pa, + size_t size, unsigned int flags) { + metal_unused(pa); + metal_unused(size); + metal_unused(flags); + return va; +} + +void metal_machine_cache_flush(void *addr, unsigned int len) { + SCB_CleanDCache_by_Addr(addr, len); +} + +void metal_machine_cache_invalidate(void *addr, unsigned int len) { + SCB_InvalidateDCache_by_Addr(addr, len); +} + +int metal_rproc_notify(void *priv, uint32_t id) { + HAL_HSEM_FastTake(METAL_HSEM_REMOTE_ID); + HAL_HSEM_Release(METAL_HSEM_REMOTE_ID, 0); + return 0; +} + +void HSEM1_IRQHandler(void) { + HAL_HSEM_IRQHandler(); + mp_sched_schedule_node(&rproc_notify_node, openamp_remoteproc_notified); + HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); +} diff --git a/ports/stm32/mpmetalport.h b/ports/stm32/mpmetalport.h new file mode 100644 index 000000000000..53f329d15b09 --- /dev/null +++ b/ports/stm32/mpmetalport.h @@ -0,0 +1,76 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * libmetal stm32 port. + */ +#ifndef MICROPY_INCLUDED_STM32_MPMETALPORT_H +#define MICROPY_INCLUDED_STM32_MPMETALPORT_H + +#include +#include "py/mphal.h" +#include "py/runtime.h" + +#define METAL_HAVE_STDATOMIC_H 0 +#define METAL_HAVE_FUTEX_H 0 + +#define METAL_MAX_DEVICE_REGIONS 2 + +#define METAL_HSEM_REMOTE_ID 0 +#define METAL_HSEM_MASTER_ID 1 + +// Set to 1 to enable log output. +#define METAL_LOG_HANDLER_ENABLE 0 + +#define metal_cpu_yield() + +// Shared memory config +#define METAL_SHM_NAME "OPENAMP_SHM" +// Note 1K must be reserved at the start of the openamp +// shared memory region, for the shared resource table. +#define METAL_RSC_ADDR ((void *)_openamp_shm_region_start) +#define METAL_RSC_SIZE (1024) + +#define METAL_SHM_ADDR ((metal_phys_addr_t)(_openamp_shm_region_start + METAL_RSC_SIZE)) +#define METAL_SHM_SIZE ((size_t)(_openamp_shm_region_end - _openamp_shm_region_start - METAL_RSC_SIZE)) + +#define METAL_MPU_REGION_BASE ((uint32_t)_openamp_shm_region_start) +#define METAL_MPU_REGION_SIZE (MPU_REGION_SIZE_64KB) + +extern const char _openamp_shm_region_start[]; +extern const char _openamp_shm_region_end[]; + +int metal_rproc_notify(void *priv, uint32_t id); +extern void openamp_remoteproc_notified(mp_sched_node_t *node); + +static inline int __metal_sleep_usec(unsigned int usec) { + mp_hal_delay_us(usec); + return 0; +} + +static inline void metal_generic_default_poll(void) { + MICROPY_EVENT_POLL_HOOK +} + +#endif // MICROPY_INCLUDED_STM32_METAL_PORT_H diff --git a/ports/stm32/mpremoteprocport.c b/ports/stm32/mpremoteprocport.c new file mode 100644 index 000000000000..56dff43da4e7 --- /dev/null +++ b/ports/stm32/mpremoteprocport.c @@ -0,0 +1,153 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * modremoteproc stm32 port. + */ + +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" +#include "metal/sys.h" +#include "metal/device.h" +#include "metal/utilities.h" +#include "extmod/modopenamp_remoteproc.h" + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +struct remoteproc *mp_openamp_remoteproc_init(struct remoteproc *rproc, + const struct remoteproc_ops *ops, void *arg) { + DEBUG_printf("rproc_init()\n"); + + rproc->ops = ops; + rproc->state = RPROC_OFFLINE; + // Allocate the image store and save it in private data. + rproc->priv = mp_openamp_remoteproc_store_alloc(); + return rproc; +} + +void *mp_openamp_remoteproc_mmap(struct remoteproc *rproc, metal_phys_addr_t *pa, + metal_phys_addr_t *da, size_t size, unsigned int attribute, + struct metal_io_region **io) { + DEBUG_printf("rproc_mmap(): pa 0x%p da 0x%p io 0x%p size %u\n", *pa, *da, *io, size); + + struct remoteproc_mem *mem; + metal_phys_addr_t lpa = *pa; + metal_phys_addr_t lda = *da; + + if (lda == METAL_BAD_PHYS) { + return NULL; + } + + if (lpa == METAL_BAD_PHYS) { + lpa = lda; + } + + // Currently this port doesn't support loading firmware to flash, + // only SD/SRAM images are supported. Check of load address is in + // the flash region, and if so return NULL. + if (lda >= FLASH_BASE && lda < FLASH_END) { + return NULL; + } + + mem = metal_allocate_memory(sizeof(*mem)); + if (!mem) { + return NULL; + } + + *io = metal_allocate_memory(sizeof(struct metal_io_region)); + if (!*io) { + metal_free_memory(mem); + return NULL; + } + + remoteproc_init_mem(mem, NULL, lpa, lda, size, *io); + + metal_io_init(*io, (void *)mem->da, &mem->pa, size, + sizeof(metal_phys_addr_t) << 3, attribute, NULL); + + remoteproc_add_mem(rproc, mem); + *pa = lpa; + *da = lda; + return metal_io_phys_to_virt(*io, mem->pa); +} + +int mp_openamp_remoteproc_start(struct remoteproc *rproc) { + DEBUG_printf("rproc_start()\n"); + if ((RCC->GCR & RCC_GCR_BOOT_C2) || (FLASH->OPTSR_CUR & FLASH_OPTSR_BCM4)) { + // The CM4 core has already been started manually, or auto-boot is enabled + // via the option bytes, in either case the core can't be restarted. + DEBUG_printf("rproc_start(): CM4 core is already booted.\n"); + return -1; + } + + // Flush M7 cache. + struct metal_list *node; + metal_list_for_each(&rproc->mems, node) { + struct remoteproc_mem *mem; + mem = metal_container_of(node, struct remoteproc_mem, node); + SCB_CleanDCache_by_Addr((uint32_t *)mem->pa, mem->size); + } + + HAL_SYSCFG_CM4BootAddConfig(SYSCFG_BOOT_ADDR0, (uint32_t)rproc->bootaddr); + HAL_RCCEx_EnableBootCore(RCC_BOOT_C2); + return 0; +} + +int mp_openamp_remoteproc_stop(struct remoteproc *rproc) { + DEBUG_printf("rproc_stop()\n"); + if (rproc->state == RPROC_RUNNING) { + // There's no straightforward way to reset or shut down + // the remote processor, so a full system reset is needed. + NVIC_SystemReset(); + } + return 0; +} + +int mp_openamp_remoteproc_config(struct remoteproc *rproc, void *data) { + DEBUG_printf("rproc_config()\n"); + (void)rproc; + return 0; +} + +void mp_openamp_remoteproc_remove(struct remoteproc *rproc) { + DEBUG_printf("rproc_remove()\n"); + (void)rproc; +} + +int mp_openamp_remoteproc_shutdown(struct remoteproc *rproc) { + DEBUG_printf("rproc_shutdown()\n"); + if (rproc->state == RPROC_RUNNING) { + // There's no straightforward way to reset or shut down + // the remote processor, so a full system reset is needed. + NVIC_SystemReset(); + } + return 0; +} diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index 64880a85db75..5ef1466184aa 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -36,6 +36,7 @@ #define MPU_REGION_QSPI3 (MPU_REGION_NUMBER3) #define MPU_REGION_SDRAM1 (MPU_REGION_NUMBER4) #define MPU_REGION_SDRAM2 (MPU_REGION_NUMBER5) +#define MPU_REGION_OPENAMP (MPU_REGION_NUMBER15) // Only relevant on CPUs with D-Cache, must be higher priority than SDRAM #define MPU_REGION_DMA_UNCACHED_1 (MPU_REGION_NUMBER6) @@ -94,6 +95,18 @@ | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos \ ) +#define MPU_CONFIG_SHARED_UNCACHED(size) ( \ + MPU_INSTRUCTION_ACCESS_DISABLE << MPU_RASR_XN_Pos \ + | MPU_REGION_FULL_ACCESS << MPU_RASR_AP_Pos \ + | MPU_TEX_LEVEL1 << MPU_RASR_TEX_Pos \ + | MPU_ACCESS_SHAREABLE << MPU_RASR_S_Pos \ + | MPU_ACCESS_NOT_CACHEABLE << MPU_RASR_C_Pos \ + | MPU_ACCESS_NOT_BUFFERABLE << MPU_RASR_B_Pos \ + | 0x00 << MPU_RASR_SRD_Pos \ + | (size) << MPU_RASR_SIZE_Pos \ + | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos \ + ) + static inline void mpu_init(void) { MPU->CTRL = MPU_PRIVILEGED_DEFAULT | MPU_CTRL_ENABLE_Msk; SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; diff --git a/ports/stm32/stm32.mk b/ports/stm32/stm32.mk index b9f70db73e0a..718fa8cf0679 100644 --- a/ports/stm32/stm32.mk +++ b/ports/stm32/stm32.mk @@ -44,14 +44,14 @@ ifneq ($(BUILDING_MBOOT),1) SUPPORTS_HARDWARE_FP_SINGLE = 0 SUPPORTS_HARDWARE_FP_DOUBLE = 0 ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx STM32H747xx STM32H750xx STM32H7A3xx STM32H7A3xxQ STM32H7B3xx STM32H7B3xxQ)) -CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard +CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 SUPPORTS_HARDWARE_FP_DOUBLE = 1 else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 g0 l0 l1 wl)) CFLAGS_CORTEX_M += -msoft-float else -CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard +CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 endif endif diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 981babca9f10..2e34055bf773 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -41,6 +41,11 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) #endif +// Don't use native _Float16 because it increases code size by a lot. +#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16 +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0) +#endif + // Enable arbitrary precision long-int by default. #ifndef MICROPY_LONGINT_IMPL #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) diff --git a/ports/webassembly/Makefile b/ports/webassembly/Makefile index c5ee80fe0046..d0a8aa99243c 100644 --- a/ports/webassembly/Makefile +++ b/ports/webassembly/Makefile @@ -1,57 +1,153 @@ -include ../../py/mkenv.mk +################################################################################ +# Initial setup of Makefile environment. + +# Select the variant to build for: +ifdef VARIANT_DIR +# Custom variant path - remove trailing slash and get the final component of +# the path as the variant name. +VARIANT ?= $(notdir $(VARIANT_DIR:/=)) +else +# If not given on the command line, then default to standard. +VARIANT ?= standard +VARIANT_DIR ?= variants/$(VARIANT) +endif + +ifeq ($(wildcard $(VARIANT_DIR)/.),) +$(error Invalid VARIANT specified: $(VARIANT_DIR)) +endif + +# If the build directory is not given, make it reflect the variant name. +BUILD ?= build-$(VARIANT) -CROSS = 0 +include ../../py/mkenv.mk +include $(VARIANT_DIR)/mpconfigvariant.mk +# Qstr definitions (must come before including py.mk). QSTR_DEFS = qstrdefsport.h +# Include py core make definitions. include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk +################################################################################ +# Project specific settings and compiler/linker flags. + CC = emcc LD = emcc +NODE ?= node +TERSER ?= npx terser INC += -I. INC += -I$(TOP) INC += -I$(BUILD) +INC += -I$(VARIANT_DIR) CFLAGS += -std=c99 -Wall -Werror -Wdouble-promotion -Wfloat-conversion CFLAGS += -Os -DNDEBUG CFLAGS += $(INC) +EXPORTED_FUNCTIONS_EXTRA += ,\ + _mp_js_do_exec,\ + _mp_js_do_exec_async,\ + _mp_js_do_import,\ + _mp_js_register_js_module,\ + _proxy_c_init,\ + _proxy_c_to_js_call,\ + _proxy_c_to_js_delete_attr,\ + _proxy_c_to_js_dir,\ + _proxy_c_to_js_get_array,\ + _proxy_c_to_js_get_dict,\ + _proxy_c_to_js_get_type,\ + _proxy_c_to_js_has_attr,\ + _proxy_c_to_js_lookup_attr,\ + _proxy_c_to_js_resume,\ + _proxy_c_to_js_store_attr,\ + _proxy_convert_mp_to_js_obj_cside + +EXPORTED_RUNTIME_METHODS_EXTRA += ,\ + PATH,\ + PATH_FS,\ + UTF8ToString,\ + getValue,\ + lengthBytesUTF8,\ + setValue,\ + stringToUTF8 + +JSFLAGS += -s EXPORTED_FUNCTIONS="\ + _free,\ + _malloc,\ + _mp_js_init,\ + _mp_js_repl_init,\ + _mp_js_repl_process_char,\ + _mp_hal_get_interrupt_char,\ + _mp_sched_keyboard_interrupt$(EXPORTED_FUNCTIONS_EXTRA)" +JSFLAGS += -s EXPORTED_RUNTIME_METHODS="\ + ccall,\ + cwrap,\ + FS$(EXPORTED_RUNTIME_METHODS_EXTRA)" +JSFLAGS += --js-library library.js +JSFLAGS += -s SUPPORT_LONGJMP=emscripten +JSFLAGS += -s MODULARIZE -s EXPORT_NAME=_createMicroPythonModule + +################################################################################ +# Source files and libraries. + SRC_SHARED = $(addprefix shared/,\ runtime/interrupt_char.c \ runtime/stdout_helpers.c \ runtime/pyexec.c \ readline/readline.c \ + timeutils/timeutils.c \ ) -SRC_C = \ +SRC_C += \ + lexer_dedent.c \ main.c \ + modjs.c \ + modjsffi.c \ mphalport.c \ + objjsproxy.c \ + proxy_c.c \ +# List of sources for qstr extraction. SRC_QSTR += $(SRC_C) $(SRC_SHARED) +SRC_JS += \ + api.js \ + objpyproxy.js \ + proxy_js.js \ + OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_SHARED:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) -JSFLAGS += -s ASYNCIFY -JSFLAGS += -s EXPORTED_FUNCTIONS="['_mp_js_init', '_mp_js_init_repl', '_mp_js_do_str', '_mp_js_process_char', '_mp_hal_get_interrupt_char', '_mp_sched_keyboard_interrupt']" -JSFLAGS += -s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap', 'FS']" -JSFLAGS += --js-library library.js +################################################################################ +# Main targets. + +.PHONY: all repl min test test_min + +all: $(BUILD)/micropython.mjs + +$(BUILD)/micropython.mjs: $(OBJ) library.js $(SRC_JS) + $(ECHO) "LINK $@" + $(Q)emcc $(LDFLAGS) -o $@ $(OBJ) $(JSFLAGS) + $(Q)cat $(SRC_JS) >> $@ + +$(BUILD)/micropython.min.mjs: $(BUILD)/micropython.mjs + $(TERSER) $< --compress --module -o $@ + +repl: $(BUILD)/micropython.mjs + $(NODE) $< -all: $(BUILD)/micropython.js +min: $(BUILD)/micropython.min.mjs -$(BUILD)/micropython.js: $(OBJ) library.js wrapper.js - $(ECHO) "LINK $(BUILD)/firmware.js" - $(Q)emcc $(LDFLAGS) -o $(BUILD)/firmware.js $(OBJ) $(JSFLAGS) - cat wrapper.js $(BUILD)/firmware.js > $@ +test: $(BUILD)/micropython.mjs $(TOP)/tests/run-tests.py + cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py --target webassembly -min: $(BUILD)/micropython.js - uglifyjs $< -c -o $(BUILD)/micropython.min.js +test_min: $(BUILD)/micropython.min.mjs $(TOP)/tests/run-tests.py + cd $(TOP)/tests && MICROPY_MICROPYTHON_MJS=../ports/webassembly/$< ./run-tests.py --target webassembly -test: $(BUILD)/micropython.js $(TOP)/tests/run-tests.py - $(eval DIRNAME=ports/$(notdir $(CURDIR))) - cd $(TOP)/tests && MICROPY_MICROPYTHON=../ports/webassembly/node_run.sh ./run-tests.py -j1 +################################################################################ +# Remaining make rules. include $(TOP)/py/mkrules.mk diff --git a/ports/webassembly/README.md b/ports/webassembly/README.md index abd2864a56cb..af395778a748 100644 --- a/ports/webassembly/README.md +++ b/ports/webassembly/README.md @@ -6,22 +6,22 @@ MicroPython for [WebAssembly](https://webassembly.org/). Dependencies ------------ -Building webassembly port bears the same requirements as the standard -MicroPython ports with the addition of Emscripten (and uglify-js for the -minified file). +Building the webassembly port bears the same requirements as the standard +MicroPython ports with the addition of Emscripten, and optionally terser for +the minified file. -The output includes `micropython.js` (a JavaScript wrapper for the -MicroPython runtime) and `firmware.wasm` (actual MicroPython compiled to +The output includes `micropython.mjs` (a JavaScript wrapper for the +MicroPython runtime) and `micropython.wasm` (actual MicroPython compiled to WASM). Build instructions ------------------ -In order to build micropython.js, run: +In order to build `micropython.mjs`, run: $ make -To generate the minified file micropython.min.js, run: +To generate the minified file `micropython.min.mjs`, run: $ make min @@ -30,55 +30,90 @@ Running with Node.js Access the repl with: - $ node build/micropython.js + $ make repl -Stack size may be modified using: +This is the same as running: - $ node build/micropython.js -X stack=64K + $ node build-standard/micropython.mjs -Where stack size may be represented in Bytes, KiB or MiB. +The initial MicroPython GC heap size may be modified using: + + $ node build-standard/micropython.mjs -X heapsize=64k + +Where stack size may be represented in bytes, or have a `k` or `m` suffix. MicroPython scripts may be executed using: - $ node build/micropython.js hello.py + $ node build-standard/micropython.mjs hello.py -Alternatively micropython.js may by accessed by other javascript programs in node +Alternatively `micropython.mjs` may by accessed by other JavaScript programs in node using the require command and the general API outlined below. For example: ```javascript -var mp_js = require('./build/micropython.js'); +const mp_mjs = await import("micropython.mjs"); +const mp = await mp_mjs.loadMicroPython(); + +mp.runPython("print('hello world')"); +``` -mp_js_init(64 * 1024); -await mp_js_do_str("print('hello world')\n"); +Or without await notation: + +```javascript +import("micropython.mjs").then((mp_mjs) => { + mp_mjs.loadMicroPython().then((mp) => { + mp.runPython("print('hello world')"); + }); +}); ``` Running with HTML ----------------- -The prerequisite for browser operation of micropython.js is to listen to the -`micropython-print` event, which is passed data when MicroPython code prints -something to stdout. The following code demonstrates basic functionality: +The following code demonstrates the simplest way to load `micropython.mjs` in a +browser, create an interpreter context, and run some Python code: + +```html + + + + + + + + + +``` + +The output in the above example will go to the JavaScript console. It's possible +to instead capture the output and print it somewhere else, for example in an +HTML element. The following example shows how to do this, and also demonstrates +the use of top-level await and the `js` module: ```html - +

-    
   
 
@@ -98,31 +133,41 @@ Run the test suite using:
 API
 ---
 
-The following functions have been exposed to javascript.
+The following functions have been exposed to JavaScript through the interpreter
+context, created and returned by `loadMicroPython()`.
 
-```
-mp_js_init(stack_size)
-```
+- `PyProxy`: the type of the object that proxies Python objects.
 
-Initialize MicroPython with the given stack size in bytes. This must be
-called before attempting to interact with MicroPython.
+- `FS`: the Emscripten filesystem object.
 
-```
-await mp_js_do_str(code)
-```
+- `globals`: an object exposing the globals from the Python `__main__` module,
+  with methods `get(key)`, `set(key, value)` and `delete(key)`.
 
-Execute the input code. `code` must be a `string`.
+- `registerJsModule(name, module)`: register a JavaScript object as importable
+  from Python with the given name.
 
-```
-mp_js_init_repl()
-```
+- `pyimport`: import a Python module and return it.
 
-Initialize MicroPython repl. Must be called before entering characters into
-the repl.
+- `runPython(code)`: execute Python code and return the result.
 
-```
-await mp_js_process_char(char)
-```
+- `runPythonAsync(code)`: execute Python code and return the result, allowing for
+  top-level await expressions (this call must be await'ed on the JavaScript side).
+
+- `replInit()`: initialise the REPL.
+
+- `replProcessChar(chr)`: process an incoming character at the REPL.
+
+- `replProcessCharWithAsyncify(chr)`: process an incoming character at the REPL,
+  for use when ASYNCIFY is enabled.
+
+Proxying
+--------
+
+A Python `dict` instance is proxied such that:
+
+    for (const key in dict) {
+        print(key, dict[key]);
+    }
 
-Input character into MicroPython repl. `char` must be of type `number`. This
-will execute MicroPython code when necessary.
+works as expected on the JavaScript side and iterates through the keys of the
+Python `dict`.
diff --git a/ports/webassembly/api.js b/ports/webassembly/api.js
new file mode 100644
index 000000000000..7d1832af4f2b
--- /dev/null
+++ b/ports/webassembly/api.js
@@ -0,0 +1,269 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// Options:
+// - heapsize: size in bytes of the MicroPython GC heap.
+// - url: location to load `micropython.mjs`.
+// - stdin: function to return input characters.
+// - stdout: function that takes one argument, and is passed lines of stdout
+//   output as they are produced.  By default this is handled by Emscripten
+//   and in a browser goes to console, in node goes to process.stdout.write.
+// - stderr: same behaviour as stdout but for error output.
+// - linebuffer: whether to buffer line-by-line to stdout/stderr.
+export async function loadMicroPython(options) {
+    const { heapsize, url, stdin, stdout, stderr, linebuffer } = Object.assign(
+        { heapsize: 1024 * 1024, linebuffer: true },
+        options,
+    );
+    const Module = {};
+    Module.locateFile = (path, scriptDirectory) =>
+        url || scriptDirectory + path;
+    Module._textDecoder = new TextDecoder();
+    if (stdin !== undefined) {
+        Module.stdin = stdin;
+    }
+    if (stdout !== undefined) {
+        if (linebuffer) {
+            Module._stdoutBuffer = [];
+            Module.stdout = (c) => {
+                if (c === 10) {
+                    stdout(
+                        Module._textDecoder.decode(
+                            new Uint8Array(Module._stdoutBuffer),
+                        ),
+                    );
+                    Module._stdoutBuffer = [];
+                } else {
+                    Module._stdoutBuffer.push(c);
+                }
+            };
+        } else {
+            Module.stdout = (c) => stdout(new Uint8Array([c]));
+        }
+    }
+    if (stderr !== undefined) {
+        if (linebuffer) {
+            Module._stderrBuffer = [];
+            Module.stderr = (c) => {
+                if (c === 10) {
+                    stderr(
+                        Module._textDecoder.decode(
+                            new Uint8Array(Module._stderrBuffer),
+                        ),
+                    );
+                    Module._stderrBuffer = [];
+                } else {
+                    Module._stderrBuffer.push(c);
+                }
+            };
+        } else {
+            Module.stderr = (c) => stderr(new Uint8Array([c]));
+        }
+    }
+    const moduleLoaded = new Promise((r) => {
+        Module.postRun = r;
+    });
+    _createMicroPythonModule(Module);
+    await moduleLoaded;
+    globalThis.Module = Module;
+    proxy_js_init();
+    const pyimport = (name) => {
+        const value = Module._malloc(3 * 4);
+        Module.ccall(
+            "mp_js_do_import",
+            "null",
+            ["string", "pointer"],
+            [name, value],
+        );
+        return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+    };
+    Module.ccall("mp_js_init", "null", ["number"], [heapsize]);
+    Module.ccall("proxy_c_init", "null", [], []);
+    return {
+        _module: Module,
+        PyProxy: PyProxy,
+        FS: Module.FS,
+        globals: {
+            __dict__: pyimport("__main__").__dict__,
+            get(key) {
+                return this.__dict__[key];
+            },
+            set(key, value) {
+                this.__dict__[key] = value;
+            },
+            delete(key) {
+                delete this.__dict__[key];
+            },
+        },
+        registerJsModule(name, module) {
+            const value = Module._malloc(3 * 4);
+            proxy_convert_js_to_mp_obj_jsside(module, value);
+            Module.ccall(
+                "mp_js_register_js_module",
+                "null",
+                ["string", "pointer"],
+                [name, value],
+            );
+            Module._free(value);
+        },
+        pyimport: pyimport,
+        runPython(code) {
+            const value = Module._malloc(3 * 4);
+            Module.ccall(
+                "mp_js_do_exec",
+                "number",
+                ["string", "pointer"],
+                [code, value],
+            );
+            return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+        },
+        runPythonAsync(code) {
+            const value = Module._malloc(3 * 4);
+            Module.ccall(
+                "mp_js_do_exec_async",
+                "number",
+                ["string", "pointer"],
+                [code, value],
+            );
+            return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+        },
+        replInit() {
+            Module.ccall("mp_js_repl_init", "null", ["null"]);
+        },
+        replProcessChar(chr) {
+            return Module.ccall(
+                "mp_js_repl_process_char",
+                "number",
+                ["number"],
+                [chr],
+            );
+        },
+        // Needed if the GC/asyncify is enabled.
+        async replProcessCharWithAsyncify(chr) {
+            return Module.ccall(
+                "mp_js_repl_process_char",
+                "number",
+                ["number"],
+                [chr],
+                { async: true },
+            );
+        },
+    };
+}
+
+globalThis.loadMicroPython = loadMicroPython;
+
+async function runCLI() {
+    const fs = await import("fs");
+    let heap_size = 128 * 1024;
+    let contents = "";
+    let repl = true;
+
+    for (let i = 2; i < process.argv.length; i++) {
+        if (process.argv[i] === "-X" && i < process.argv.length - 1) {
+            if (process.argv[i + 1].includes("heapsize=")) {
+                heap_size = parseInt(process.argv[i + 1].split("heapsize=")[1]);
+                const suffix = process.argv[i + 1].substr(-1).toLowerCase();
+                if (suffix === "k") {
+                    heap_size *= 1024;
+                } else if (suffix === "m") {
+                    heap_size *= 1024 * 1024;
+                }
+                ++i;
+            }
+        } else {
+            contents += fs.readFileSync(process.argv[i], "utf8");
+            repl = false;
+        }
+    }
+
+    if (process.stdin.isTTY === false) {
+        contents = fs.readFileSync(0, "utf8");
+        repl = false;
+    }
+
+    const mp = await loadMicroPython({
+        heapsize: heap_size,
+        stdout: (data) => process.stdout.write(data),
+        linebuffer: false,
+    });
+
+    if (repl) {
+        mp.replInit();
+        process.stdin.setRawMode(true);
+        process.stdin.on("data", (data) => {
+            for (let i = 0; i < data.length; i++) {
+                mp.replProcessCharWithAsyncify(data[i]).then((result) => {
+                    if (result) {
+                        process.exit();
+                    }
+                });
+            }
+        });
+    } else {
+        try {
+            mp.runPython(contents);
+        } catch (error) {
+            if (error.name === "PythonError") {
+                if (error.type === "SystemExit") {
+                    // SystemExit, this is a valid exception to successfully end a script.
+                } else {
+                    // An unhandled Python exception, print in out.
+                    console.error(error.message);
+                }
+            } else {
+                // A non-Python exception.  Re-raise it.
+                throw error;
+            }
+        }
+    }
+}
+
+// Check if Node is running (equivalent to ENVIRONMENT_IS_NODE).
+if (
+    typeof process === "object" &&
+    typeof process.versions === "object" &&
+    typeof process.versions.node === "string"
+) {
+    // Check if this module is ron from the command line.
+    //
+    // See https://stackoverflow.com/questions/6398196/detect-if-called-through-require-or-directly-by-command-line/66309132#66309132
+    //
+    // Note:
+    // - `resolve()` is used to handle symlinks
+    // - `includes()` is used to handle cases where the file extension was omitted when passed to node
+
+    const path = await import("path");
+    const url = await import("url");
+
+    const pathToThisFile = path.resolve(url.fileURLToPath(import.meta.url));
+    const pathPassedToNode = path.resolve(process.argv[1]);
+    const isThisFileBeingRunViaCLI = pathToThisFile.includes(pathPassedToNode);
+
+    if (isThisFileBeingRunViaCLI) {
+        runCLI();
+    }
+}
diff --git a/ports/webassembly/lexer_dedent.c b/ports/webassembly/lexer_dedent.c
new file mode 100644
index 000000000000..555caea89692
--- /dev/null
+++ b/ports/webassembly/lexer_dedent.c
@@ -0,0 +1,105 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023 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 "lexer_dedent.h"
+
+typedef struct _mp_reader_mem_dedent_t {
+    size_t free_len; // if >0 mem is freed on close by: m_free(beg, free_len)
+    const byte *beg;
+    const byte *cur;
+    const byte *end;
+    size_t dedent_prefix;
+} mp_reader_mem_dedent_t;
+
+// Work out the amount of common whitespace among all non-empty lines.
+static size_t dedent(const byte *text, size_t len) {
+    size_t min_prefix = -1;
+    size_t cur_prefix = 0;
+    bool start_of_line = true;
+    for (const byte *t = text; t < text + len; ++t) {
+        if (*t == '\n') {
+            start_of_line = true;
+            cur_prefix = 0;
+        } else if (start_of_line) {
+            if (unichar_isspace(*t)) {
+                ++cur_prefix;
+            } else {
+                if (cur_prefix < min_prefix) {
+                    min_prefix = cur_prefix;
+                    if (min_prefix == 0) {
+                        return min_prefix;
+                    }
+                }
+                start_of_line = false;
+            }
+        }
+    }
+    return min_prefix;
+}
+
+static mp_uint_t mp_reader_mem_dedent_readbyte(void *data) {
+    mp_reader_mem_dedent_t *reader = (mp_reader_mem_dedent_t *)data;
+    if (reader->cur < reader->end) {
+        byte c = *reader->cur++;
+        if (c == '\n') {
+            for (size_t i = 0; i < reader->dedent_prefix; ++i) {
+                if (*reader->cur == '\n') {
+                    break;
+                }
+                ++reader->cur;
+            }
+        }
+        return c;
+    } else {
+        return MP_READER_EOF;
+    }
+}
+
+static void mp_reader_mem_dedent_close(void *data) {
+    mp_reader_mem_dedent_t *reader = (mp_reader_mem_dedent_t *)data;
+    if (reader->free_len > 0) {
+        m_del(char, (char *)reader->beg, reader->free_len);
+    }
+    m_del_obj(mp_reader_mem_dedent_t, reader);
+}
+
+static void mp_reader_new_mem_dedent(mp_reader_t *reader, const byte *buf, size_t len, size_t free_len) {
+    mp_reader_mem_dedent_t *rm = m_new_obj(mp_reader_mem_dedent_t);
+    rm->free_len = free_len;
+    rm->beg = buf;
+    rm->cur = buf;
+    rm->end = buf + len;
+    rm->dedent_prefix = dedent(buf, len);
+    reader->data = rm;
+    reader->readbyte = mp_reader_mem_dedent_readbyte;
+    reader->close = mp_reader_mem_dedent_close;
+}
+
+mp_lexer_t *mp_lexer_new_from_str_len_dedent(qstr src_name, const char *str, size_t len, size_t free_len) {
+    mp_reader_t reader;
+    mp_reader_new_mem_dedent(&reader, (const byte *)str, len, free_len);
+    return mp_lexer_new(src_name, reader);
+}
diff --git a/shared/tinyusb/mp_usbd_internal.h b/ports/webassembly/lexer_dedent.h
similarity index 67%
rename from shared/tinyusb/mp_usbd_internal.h
rename to ports/webassembly/lexer_dedent.h
index 8b8f50bae88e..a8cc2526b4f5 100644
--- a/shared/tinyusb/mp_usbd_internal.h
+++ b/ports/webassembly/lexer_dedent.h
@@ -3,7 +3,7 @@
  *
  * The MIT License (MIT)
  *
- * Copyright (c) 2022 Angus Gratton
+ * Copyright (c) 2023-2024 Damien P. George
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -23,12 +23,14 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-#ifndef MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H
-#define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H
-#include "tusb.h"
+#ifndef MICROPY_INCLUDED_WEBASSEMBLY_LEXER_DEDENT_H
+#define MICROPY_INCLUDED_WEBASSEMBLY_LEXER_DEDENT_H
 
-// Static USB device descriptor values
-extern const tusb_desc_device_t mp_usbd_desc_device_static;
-extern const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN];
+#include "py/lexer.h"
 
-#endif // MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H
+// This function creates a new "dedenting lexer" which automatically dedents the input
+// source code if every non-empty line in that source starts with a common whitespace
+// prefix.  It does this dedenting inplace as the memory is read.
+mp_lexer_t *mp_lexer_new_from_str_len_dedent(qstr src_name, const char *str, size_t len, size_t free_len);
+
+#endif // MICROPY_INCLUDED_WEBASSEMBLY_LEXER_DEDENT_H
diff --git a/ports/webassembly/library.h b/ports/webassembly/library.h
index 47982e064007..04b408d71a25 100644
--- a/ports/webassembly/library.h
+++ b/ports/webassembly/library.h
@@ -29,3 +29,5 @@
 extern void mp_js_write(const char *str, mp_uint_t len);
 extern int mp_js_ticks_ms(void);
 extern void mp_js_hook(void);
+extern double mp_js_time_ms(void);
+extern uint32_t mp_js_random_u32(void);
diff --git a/ports/webassembly/library.js b/ports/webassembly/library.js
index 009b0a4dcc20..3f6c9cb61f18 100644
--- a/ports/webassembly/library.js
+++ b/ports/webassembly/library.js
@@ -25,41 +25,51 @@
  */
 
 mergeInto(LibraryManager.library, {
-    mp_js_write: function(ptr, len) {
-        const buffer = HEAPU8.subarray(ptr, ptr + len)
-        if (ENVIRONMENT_IS_NODE) {
-            process.stdout.write(buffer);
-        } else {
-            const printEvent = new CustomEvent('micropython-print', { detail: buffer });
-            document.dispatchEvent(printEvent);
-        }
-    },
+    // This string will be emitted directly into the output file by Emscripten.
+    mp_js_ticks_ms__postset: "var MP_JS_EPOCH = Date.now()",
 
-    mp_js_ticks_ms: function() {
-        return Date.now() - MP_JS_EPOCH;
-    },
+    mp_js_ticks_ms: () => Date.now() - MP_JS_EPOCH,
 
-    mp_js_hook: function() {
+    mp_js_hook: () => {
         if (ENVIRONMENT_IS_NODE) {
-            var mp_interrupt_char = Module.ccall('mp_hal_get_interrupt_char', 'number', ['number'], ['null']);
-            var fs = require('fs');
+            const mp_interrupt_char = Module.ccall(
+                "mp_hal_get_interrupt_char",
+                "number",
+                ["number"],
+                ["null"],
+            );
+            const fs = require("fs");
 
-            var buf = Buffer.alloc(1);
+            const buf = Buffer.alloc(1);
             try {
-                var n = fs.readSync(process.stdin.fd, buf, 0, 1);
+                const n = fs.readSync(process.stdin.fd, buf, 0, 1);
                 if (n > 0) {
-                    if (buf[0] == mp_interrupt_char) {
-                        Module.ccall('mp_sched_keyboard_interrupt', 'null', ['null'], ['null']);
+                    if (buf[0] === mp_interrupt_char) {
+                        Module.ccall(
+                            "mp_sched_keyboard_interrupt",
+                            "null",
+                            ["null"],
+                            ["null"],
+                        );
                     } else {
                         process.stdout.write(String.fromCharCode(buf[0]));
                     }
                 }
             } catch (e) {
-                if (e.code === 'EAGAIN') {
+                if (e.code === "EAGAIN") {
                 } else {
                     throw e;
                 }
             }
         }
     },
+
+    mp_js_time_ms: () => Date.now(),
+
+    // Node prior to v19 did not expose "crypto" as a global, so make sure it exists.
+    mp_js_random_u32__postset:
+        "if (globalThis.crypto === undefined) { globalThis.crypto = require('crypto'); }",
+
+    mp_js_random_u32: () =>
+        globalThis.crypto.getRandomValues(new Uint32Array(1))[0],
 });
diff --git a/ports/webassembly/main.c b/ports/webassembly/main.c
index 0aacf1ee095b..441c6d67e350 100644
--- a/ports/webassembly/main.c
+++ b/ports/webassembly/main.c
@@ -40,47 +40,9 @@
 #include "shared/runtime/pyexec.h"
 
 #include "emscripten.h"
+#include "lexer_dedent.h"
 #include "library.h"
-
-#if MICROPY_ENABLE_COMPILER
-int do_str(const char *src, mp_parse_input_kind_t input_kind) {
-    int ret = 0;
-    nlr_buf_t nlr;
-    if (nlr_push(&nlr) == 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, input_kind);
-        mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false);
-        mp_call_function_0(module_fun);
-        nlr_pop();
-    } else {
-        // uncaught exception
-        if (mp_obj_is_subclass_fast(mp_obj_get_type((mp_obj_t)nlr.ret_val), &mp_type_SystemExit)) {
-            mp_obj_t exit_val = mp_obj_exception_get_value(MP_OBJ_FROM_PTR(nlr.ret_val));
-            if (exit_val != mp_const_none) {
-                mp_int_t int_val;
-                if (mp_obj_get_int_maybe(exit_val, &int_val)) {
-                    ret = int_val & 255;
-                } else {
-                    ret = 1;
-                }
-            }
-        } else {
-            mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
-            ret = 1;
-        }
-    }
-    return ret;
-}
-#endif
-
-int mp_js_do_str(const char *code) {
-    return do_str(code, MP_PARSE_FILE_INPUT);
-}
-
-int mp_js_process_char(int c) {
-    return pyexec_event_repl_process_char(c);
-}
+#include "proxy_c.h"
 
 void mp_js_init(int heap_size) {
     #if MICROPY_ENABLE_GC
@@ -105,13 +67,91 @@ void mp_js_init(int heap_size) {
         mp_vfs_mount(2, args, (mp_map_t *)&mp_const_empty_map);
         MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table);
     }
+    mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_lib));
     #endif
 }
 
-void mp_js_init_repl() {
+void mp_js_register_js_module(const char *name, uint32_t *value) {
+    mp_obj_t module_name = MP_OBJ_NEW_QSTR(qstr_from_str(name));
+    mp_obj_t module = proxy_convert_js_to_mp_obj_cside(value);
+    mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map;
+    mp_map_lookup(mp_loaded_modules_map, module_name, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = module;
+}
+
+void mp_js_do_import(const char *name, uint32_t *out) {
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t ret = mp_import_name(qstr_from_str(name), mp_const_none, MP_OBJ_NEW_SMALL_INT(0));
+        // Return the leaf of the import, eg for "a.b.c" return "c".
+        const char *m = name;
+        const char *n = name;
+        for (;; ++n) {
+            if (*n == '\0' || *n == '.') {
+                if (m != name) {
+                    ret = mp_load_attr(ret, qstr_from_strn(m, n - m));
+                }
+                m = n + 1;
+                if (*n == '\0') {
+                    break;
+                }
+            }
+        }
+        nlr_pop();
+        proxy_convert_mp_to_js_obj_cside(ret, out);
+    } else {
+        // uncaught exception
+        proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+    }
+}
+
+void mp_js_do_exec(const char *src, uint32_t *out) {
+    // Collect at the top-level, where there are no root pointers from stack/registers.
+    gc_collect_start();
+    gc_collect_end();
+
+    mp_parse_input_kind_t input_kind = MP_PARSE_FILE_INPUT;
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_lexer_t *lex = mp_lexer_new_from_str_len_dedent(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
+        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);
+        mp_obj_t ret = mp_call_function_0(module_fun);
+        nlr_pop();
+        proxy_convert_mp_to_js_obj_cside(ret, out);
+    } else {
+        // uncaught exception
+        proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+    }
+}
+
+void mp_js_do_exec_async(const char *src, uint32_t *out) {
+    mp_compile_allow_top_level_await = true;
+    mp_js_do_exec(src, out);
+    mp_compile_allow_top_level_await = false;
+}
+
+void mp_js_repl_init(void) {
     pyexec_event_repl_init();
 }
 
+int mp_js_repl_process_char(int c) {
+    return pyexec_event_repl_process_char(c);
+}
+
+#if MICROPY_GC_SPLIT_HEAP_AUTO
+
+// The largest new region that is available to become Python heap.
+size_t gc_get_max_new_split(void) {
+    return 128 * 1024 * 1024;
+}
+
+// Don't collect anything.  Instead require the heap to grow.
+void gc_collect(void) {
+}
+
+#else
+
 static void gc_scan_func(void *begin, void *end) {
     gc_collect_root((void **)begin, (void **)end - (void **)begin + 1);
 }
@@ -123,6 +163,8 @@ void gc_collect(void) {
     gc_collect_end();
 }
 
+#endif
+
 #if !MICROPY_VFS
 mp_lexer_t *mp_lexer_new_from_file(qstr filename) {
     mp_raise_OSError(MP_ENOENT);
diff --git a/ports/webassembly/modjs.c b/ports/webassembly/modjs.c
new file mode 100644
index 000000000000..bed09086ab7c
--- /dev/null
+++ b/ports/webassembly/modjs.c
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/objmodule.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+#if MICROPY_PY_JS
+
+/******************************************************************************/
+// js module
+
+void mp_module_js_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+    mp_obj_jsproxy_t global_this;
+    global_this.ref = 0;
+    mp_obj_jsproxy_attr(MP_OBJ_FROM_PTR(&global_this), attr, dest);
+}
+
+static const mp_rom_map_elem_t mp_module_js_globals_table[] = {
+    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_js) },
+};
+static MP_DEFINE_CONST_DICT(mp_module_js_globals, mp_module_js_globals_table);
+
+const mp_obj_module_t mp_module_js = {
+    .base = { &mp_type_module },
+    .globals = (mp_obj_dict_t *)&mp_module_js_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_js, mp_module_js);
+MP_REGISTER_MODULE_DELEGATION(mp_module_js, mp_module_js_attr);
+
+#endif // MICROPY_PY_JS
diff --git a/ports/webassembly/modjsffi.c b/ports/webassembly/modjsffi.c
new file mode 100644
index 000000000000..d4e61e368f00
--- /dev/null
+++ b/ports/webassembly/modjsffi.c
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "emscripten.h"
+#include "py/objmodule.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+#if MICROPY_PY_JSFFI
+
+/******************************************************************************/
+// jsffi module
+
+EM_JS(void, proxy_convert_mp_to_js_then_js_to_mp_obj_jsside, (uint32_t * out), {
+    const ret = proxy_convert_mp_to_js_obj_jsside(out);
+    proxy_convert_js_to_mp_obj_jsside_force_double_proxy(ret, out);
+});
+
+static mp_obj_t mp_jsffi_create_proxy(mp_obj_t arg) {
+    uint32_t out[3];
+    proxy_convert_mp_to_js_obj_cside(arg, out);
+    proxy_convert_mp_to_js_then_js_to_mp_obj_jsside(out);
+    return proxy_convert_js_to_mp_obj_cside(out);
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_create_proxy_obj, mp_jsffi_create_proxy);
+
+EM_JS(void, proxy_convert_mp_to_js_then_js_to_js_then_js_to_mp_obj_jsside, (uint32_t * out), {
+    const ret = proxy_convert_mp_to_js_obj_jsside(out);
+    const js_obj = PyProxy.toJs(ret);
+    proxy_convert_js_to_mp_obj_jsside(js_obj, out);
+});
+
+static mp_obj_t mp_jsffi_to_js(mp_obj_t arg) {
+    uint32_t out[3];
+    proxy_convert_mp_to_js_obj_cside(arg, out);
+    proxy_convert_mp_to_js_then_js_to_js_then_js_to_mp_obj_jsside(out);
+    return proxy_convert_js_to_mp_obj_cside(out);
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_to_js_obj, mp_jsffi_to_js);
+
+static const mp_rom_map_elem_t mp_module_jsffi_globals_table[] = {
+    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jsffi) },
+
+    { MP_ROM_QSTR(MP_QSTR_JsProxy), MP_ROM_PTR(&mp_type_jsproxy) },
+    { MP_ROM_QSTR(MP_QSTR_create_proxy), MP_ROM_PTR(&mp_jsffi_create_proxy_obj) },
+    { MP_ROM_QSTR(MP_QSTR_to_js), MP_ROM_PTR(&mp_jsffi_to_js_obj) },
+};
+static MP_DEFINE_CONST_DICT(mp_module_jsffi_globals, mp_module_jsffi_globals_table);
+
+const mp_obj_module_t mp_module_jsffi = {
+    .base = { &mp_type_module },
+    .globals = (mp_obj_dict_t *)&mp_module_jsffi_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_jsffi, mp_module_jsffi);
+
+#endif // MICROPY_PY_JSFFI
diff --git a/ports/webassembly/modtime.c b/ports/webassembly/modtime.c
new file mode 100644
index 000000000000..1b1e63d4ddf3
--- /dev/null
+++ b/ports/webassembly/modtime.c
@@ -0,0 +1,51 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023 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/obj.h"
+#include "shared/timeutils/timeutils.h"
+#include "library.h"
+
+// Return the localtime as an 8-tuple.
+static mp_obj_t mp_time_localtime_get(void) {
+    timeutils_struct_time_t tm;
+    timeutils_seconds_since_epoch_to_struct_time(mp_hal_time_ms() / 1000, &tm);
+    mp_obj_t tuple[8] = {
+        mp_obj_new_int(tm.tm_year),
+        mp_obj_new_int(tm.tm_mon),
+        mp_obj_new_int(tm.tm_mday),
+        mp_obj_new_int(tm.tm_hour),
+        mp_obj_new_int(tm.tm_min),
+        mp_obj_new_int(tm.tm_sec),
+        mp_obj_new_int(tm.tm_wday),
+        mp_obj_new_int(tm.tm_yday),
+    };
+    return mp_obj_new_tuple(8, tuple);
+}
+
+// Returns the number of seconds, as a float, since the Epoch.
+static mp_obj_t mp_time_time_get(void) {
+    return mp_obj_new_float((mp_float_t)mp_hal_time_ms() / 1000);
+}
diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h
index e0d1af0cce2b..ae5dfa6fa50b 100644
--- a/ports/webassembly/mpconfigport.h
+++ b/ports/webassembly/mpconfigport.h
@@ -25,18 +25,21 @@
  * THE SOFTWARE.
  */
 
+// Options to control how MicroPython is built for this port, overriding
+// defaults in py/mpconfig.h.
+
 #include 
+#include  // for malloc, for MICROPY_GC_SPLIT_HEAP_AUTO
 
-// options to control how MicroPython is built
+// Variant-specific definitions.
+#include "mpconfigvariant.h"
 
+#ifndef MICROPY_CONFIG_ROM_LEVEL
 #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES)
-
-// You can disable the built-in MicroPython compiler by setting the following
-// config option to 0.  If you do this then you won't get a REPL prompt, but you
-// will still be able to execute pre-compiled scripts, compiled with mpy-cross.
-#define MICROPY_ENABLE_COMPILER     (1)
+#endif
 
 #define MICROPY_ALLOC_PATH_MAX      (256)
+#define MICROPY_COMP_ALLOW_TOP_LEVEL_AWAIT (1)
 #define MICROPY_READER_VFS          (MICROPY_VFS)
 #define MICROPY_ENABLE_GC           (1)
 #define MICROPY_ENABLE_PYSTACK      (1)
@@ -46,15 +49,29 @@
 #define MICROPY_LONGINT_IMPL        (MICROPY_LONGINT_IMPL_MPZ)
 #define MICROPY_ENABLE_DOC_STRING   (1)
 #define MICROPY_WARNINGS            (1)
+#define MICROPY_ERROR_PRINTER       (&mp_stderr_print)
 #define MICROPY_FLOAT_IMPL          (MICROPY_FLOAT_IMPL_DOUBLE)
 #define MICROPY_USE_INTERNAL_ERRNO  (1)
 #define MICROPY_USE_INTERNAL_PRINTF (0)
+
+#define MICROPY_EPOCH_IS_1970       (1)
+#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (mp_js_random_u32())
+#define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1)
+#define MICROPY_PY_TIME_TIME_TIME_NS (1)
+#define MICROPY_PY_TIME_INCLUDEFILE "ports/webassembly/modtime.c"
 #ifndef MICROPY_VFS
 #define MICROPY_VFS                 (1)
 #endif
 #define MICROPY_VFS_POSIX           (MICROPY_VFS)
 #define MICROPY_PY_SYS_PLATFORM     "webassembly"
-#define MICROPY_PY_SYS_STDFILES     (0)
+
+#ifndef MICROPY_PY_JS
+#define MICROPY_PY_JS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
+
+#ifndef MICROPY_PY_JSFFI
+#define MICROPY_PY_JSFFI (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
 
 #define MICROPY_EVENT_POLL_HOOK \
     do { \
@@ -62,6 +79,13 @@
         mp_handle_pending(true); \
     } while (0);
 
+// Whether the VM will periodically call mp_js_hook(), which checks for
+// interrupt characters on stdin (or equivalent input).
+#ifndef MICROPY_VARIANT_ENABLE_JS_HOOK
+#define MICROPY_VARIANT_ENABLE_JS_HOOK (0)
+#endif
+
+#if MICROPY_VARIANT_ENABLE_JS_HOOK
 #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) { \
@@ -71,6 +95,7 @@
 }
 #define MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_POLL
 #define MICROPY_VM_HOOK_RETURN MICROPY_VM_HOOK_POLL
+#endif
 
 // type definitions for the specific machine
 
@@ -95,3 +120,7 @@ typedef long mp_off_t;
 // _GNU_SOURCE must be defined to get definitions of DT_xxx symbols from dirent.h.
 #define _GNU_SOURCE
 #endif
+
+extern const struct _mp_print_t mp_stderr_print;
+
+uint32_t mp_js_random_u32(void);
diff --git a/ports/webassembly/mphalport.c b/ports/webassembly/mphalport.c
index f91a509013a3..9ab47762e3e9 100644
--- a/ports/webassembly/mphalport.c
+++ b/ports/webassembly/mphalport.c
@@ -24,12 +24,19 @@
  * THE SOFTWARE.
  */
 
+#include 
 #include "library.h"
 #include "mphalport.h"
 
+static void stderr_print_strn(void *env, const char *str, size_t len) {
+    (void)env;
+    write(2, str, len);
+}
+
+const mp_print_t mp_stderr_print = {NULL, stderr_print_strn};
+
 mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) {
-    mp_js_write(str, len);
-    return len;
+    return write(1, str, len);
 }
 
 void mp_hal_delay_ms(mp_uint_t ms) {
@@ -56,6 +63,15 @@ mp_uint_t mp_hal_ticks_cpu(void) {
     return 0;
 }
 
+uint64_t mp_hal_time_ms(void) {
+    double mm = mp_js_time_ms();
+    return (uint64_t)mm;
+}
+
+uint64_t mp_hal_time_ns(void) {
+    return mp_hal_time_ms() * 1000000ULL;
+}
+
 extern int mp_interrupt_char;
 
 int mp_hal_get_interrupt_char(void) {
diff --git a/ports/webassembly/mphalport.h b/ports/webassembly/mphalport.h
index 1b3179698644..a90de8ec5fb0 100644
--- a/ports/webassembly/mphalport.h
+++ b/ports/webassembly/mphalport.h
@@ -35,6 +35,7 @@ void mp_hal_delay_us(mp_uint_t us);
 mp_uint_t mp_hal_ticks_ms(void);
 mp_uint_t mp_hal_ticks_us(void);
 mp_uint_t mp_hal_ticks_cpu(void);
+uint64_t mp_hal_time_ms(void);
 
 int mp_hal_get_interrupt_char(void);
 
diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c
new file mode 100644
index 000000000000..5e2aeb6a36e4
--- /dev/null
+++ b/ports/webassembly/objjsproxy.c
@@ -0,0 +1,487 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include 
+#include 
+
+#include "emscripten.h"
+#include "py/objmodule.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+EM_JS(bool, has_attr, (int jsref, const char *str), {
+    const base = proxy_js_ref[jsref];
+    const attr = UTF8ToString(str);
+    if (attr in base) {
+        return true;
+    } else {
+        return false;
+    }
+});
+
+// *FORMAT-OFF*
+EM_JS(bool, lookup_attr, (int jsref, const char *str, uint32_t * out), {
+    const base = proxy_js_ref[jsref];
+    const attr = UTF8ToString(str);
+    if (attr in base) {
+        let value = base[attr];
+        if (typeof value == "function") {
+            if (base !== globalThis) {
+                value = value.bind(base);
+            }
+        }
+        proxy_convert_js_to_mp_obj_jsside(value, out);
+        return true;
+    } else {
+        return false;
+    }
+});
+// *FORMAT-ON*
+
+EM_JS(void, store_attr, (int jsref, const char *attr_ptr, uint32_t * value_ref), {
+    const attr = UTF8ToString(attr_ptr);
+    const value = proxy_convert_mp_to_js_obj_jsside(value_ref);
+    proxy_js_ref[jsref][attr] = value;
+});
+
+EM_JS(void, call0, (int f_ref, uint32_t * out), {
+    // Because of JavaScript "this" semantics, we must extract the target function
+    // to a variable before calling it, so "this" is bound to the correct value.
+    //
+    // In detail:
+    // In JavaScript, proxy_js_ref[f_ref] acts like a function call
+    // proxy_js_ref.at(f_ref), and "this" will be bound to proxy_js_ref if
+    // there is a chain of calls, such as proxy_js_ref.at(f_ref)().
+    // But proxy_js_ref is not "this" in the context of the call, so we
+    // must extract the function to an independent variable and then call
+    // that variable, so that "this" is correct (it will be "undefined").
+
+    const f = proxy_js_ref[f_ref];
+    const ret = f();
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, call1, (int f_ref, uint32_t * a0, uint32_t * out), {
+    const a0_js = proxy_convert_mp_to_js_obj_jsside(a0);
+    const f = proxy_js_ref[f_ref];
+    const ret = f(a0_js);
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, call2, (int f_ref, uint32_t * a0, uint32_t * a1, uint32_t * out), {
+    const a0_js = proxy_convert_mp_to_js_obj_jsside(a0);
+    const a1_js = proxy_convert_mp_to_js_obj_jsside(a1);
+    const f = proxy_js_ref[f_ref];
+    const ret = f(a0_js, a1_js);
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, calln, (int f_ref, uint32_t n_args, uint32_t * value, uint32_t * out), {
+    const f = proxy_js_ref[f_ref];
+    const a = [];
+    for (let i = 0; i < n_args; ++i) {
+        const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
+        a.push(v);
+    }
+    const ret = f(... a);
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, call0_kwarg, (int f_ref, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
+    const f = proxy_js_ref[f_ref];
+    const a = {};
+    for (let i = 0; i < n_kw; ++i) {
+        const k = UTF8ToString(getValue(key + i * 4, "i32"));
+        const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
+        a[k] = v;
+    }
+    const ret = f(a);
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, call1_kwarg, (int f_ref, uint32_t * arg0, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
+    const f = proxy_js_ref[f_ref];
+    const a0 = proxy_convert_mp_to_js_obj_jsside(arg0);
+    const a = {};
+    for (let i = 0; i < n_kw; ++i) {
+        const k = UTF8ToString(getValue(key + i * 4, "i32"));
+        const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
+        a[k] = v;
+    }
+    const ret = f(a0, a);
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, js_reflect_construct, (int f_ref, uint32_t n_args, uint32_t * args, uint32_t * out), {
+    const f = proxy_js_ref[f_ref];
+    const as = [];
+    for (let i = 0; i < n_args; ++i) {
+        as.push(proxy_convert_mp_to_js_obj_jsside(args + i * 4));
+    }
+    const ret = Reflect.construct(f, as);
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(int, js_get_len, (int f_ref), {
+    return proxy_js_ref[f_ref].length;
+});
+
+EM_JS(void, js_subscr_int, (int f_ref, int idx, uint32_t * out), {
+    const f = proxy_js_ref[f_ref];
+    const ret = f[idx];
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, js_subscr_load, (int f_ref, uint32_t * index_ref, uint32_t * out), {
+    const target = proxy_js_ref[f_ref];
+    const index = python_index_semantics(target, proxy_convert_mp_to_js_obj_jsside(index_ref));
+    const ret = target[index];
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+
+EM_JS(void, js_subscr_store, (int f_ref, uint32_t * idx, uint32_t * value), {
+    const f = proxy_js_ref[f_ref];
+    f[proxy_convert_mp_to_js_obj_jsside(idx)] = proxy_convert_mp_to_js_obj_jsside(value);
+});
+
+static void jsproxy_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+    mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+    mp_printf(print, "", self->ref);
+}
+
+static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+    mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+
+    if (n_kw == 0) {
+        mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, false);
+    } else {
+        mp_arg_check_num(n_args, n_kw, 0, 1, true);
+        uint32_t key[n_kw];
+        uint32_t value[PVN * n_kw];
+        for (int i = 0; i < n_kw; ++i) {
+            key[i] = (uintptr_t)mp_obj_str_get_str(args[n_args + i * 2]);
+            proxy_convert_mp_to_js_obj_cside(args[n_args + i * 2 + 1], &value[i * PVN]);
+        }
+        uint32_t out[3];
+        if (n_args == 0) {
+            call0_kwarg(self->ref, n_kw, key, value, out);
+        } else {
+            // n_args == 1
+            uint32_t arg0[PVN];
+            proxy_convert_mp_to_js_obj_cside(args[0], arg0);
+            call1_kwarg(self->ref, arg0, n_kw, key, value, out);
+        }
+        return proxy_convert_js_to_mp_obj_cside(out);
+    }
+
+    if (n_args == 0) {
+        uint32_t out[3];
+        call0(self->ref, out);
+        return proxy_convert_js_to_mp_obj_cside(out);
+    } else if (n_args == 1) {
+        uint32_t arg0[PVN];
+        uint32_t out[PVN];
+        proxy_convert_mp_to_js_obj_cside(args[0], arg0);
+        call1(self->ref, arg0, out);
+        return proxy_convert_js_to_mp_obj_cside(out);
+    } else if (n_args == 2) {
+        uint32_t arg0[PVN];
+        proxy_convert_mp_to_js_obj_cside(args[0], arg0);
+        uint32_t arg1[PVN];
+        proxy_convert_mp_to_js_obj_cside(args[1], arg1);
+        uint32_t out[3];
+        call2(self->ref, arg0, arg1, out);
+        return proxy_convert_js_to_mp_obj_cside(out);
+    } else {
+        uint32_t value[PVN * n_args];
+        for (int i = 0; i < n_args; ++i) {
+            proxy_convert_mp_to_js_obj_cside(args[i], &value[i * PVN]);
+        }
+        uint32_t out[3];
+        calln(self->ref, n_args, value, out);
+        return proxy_convert_js_to_mp_obj_cside(out);
+    }
+}
+
+static mp_obj_t jsproxy_reflect_construct(size_t n_args, const mp_obj_t *args) {
+    int arg0 = mp_obj_jsproxy_get_ref(args[0]);
+    n_args -= 1;
+    args += 1;
+    uint32_t args_conv[n_args];
+    for (unsigned int i = 0; i < n_args; ++i) {
+        proxy_convert_mp_to_js_obj_cside(args[i], &args_conv[i * PVN]);
+    }
+    uint32_t out[3];
+    js_reflect_construct(arg0, n_args, args_conv, out);
+    return proxy_convert_js_to_mp_obj_cside(out);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(jsproxy_reflect_construct_obj, 1, jsproxy_reflect_construct);
+
+static mp_obj_t jsproxy_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
+    mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+    if (value == MP_OBJ_SENTINEL) {
+        // Load subscript.
+        uint32_t idx[PVN], out[PVN];
+        proxy_convert_mp_to_js_obj_cside(index, idx);
+        js_subscr_load(self->ref, idx, out);
+        return proxy_convert_js_to_mp_obj_cside(out);
+    } else if (value == MP_OBJ_NULL) {
+        // Delete subscript.
+        return MP_OBJ_NULL; // not supported
+    } else {
+        // Store subscript.
+        uint32_t idx[PVN], val[PVN];
+        proxy_convert_mp_to_js_obj_cside(index, idx);
+        proxy_convert_mp_to_js_obj_cside(value, val);
+        js_subscr_store(self->ref, idx, val);
+        return mp_const_none;
+    }
+}
+
+void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+    mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+    if (dest[0] == MP_OBJ_NULL) {
+        // Load attribute.
+        uint32_t out[PVN];
+        if (lookup_attr(self->ref, qstr_str(attr), out)) {
+            dest[0] = proxy_convert_js_to_mp_obj_cside(out);
+        } else if (attr == MP_QSTR_new) {
+            // Special case to handle construction of JS objects.
+            // JS objects don't have a ".new" attribute, doing "Obj.new" is a Pyodide idiom for "new Obj".
+            // It translates to the JavaScript "Reflect.construct(Obj, Array(...args))".
+            dest[0] = MP_OBJ_FROM_PTR(&jsproxy_reflect_construct_obj);
+            dest[1] = self_in;
+        }
+    } else if (dest[1] == MP_OBJ_NULL) {
+        // Delete attribute.
+    } else {
+        // Store attribute.
+        uint32_t value[PVN];
+        proxy_convert_mp_to_js_obj_cside(dest[1], value);
+        store_attr(self->ref, qstr_str(attr), value);
+        dest[0] = MP_OBJ_NULL;
+    }
+}
+
+/******************************************************************************/
+// jsproxy iterator
+
+typedef struct _jsproxy_it_t {
+    mp_obj_base_t base;
+    mp_fun_1_t iternext;
+    int ref;
+    uint16_t cur;
+    uint16_t len;
+} jsproxy_it_t;
+
+static mp_obj_t jsproxy_it_iternext(mp_obj_t self_in) {
+    jsproxy_it_t *self = MP_OBJ_TO_PTR(self_in);
+    if (self->cur < self->len) {
+        uint32_t out[3];
+        js_subscr_int(self->ref, self->cur, out);
+        self->cur += 1;
+        return proxy_convert_js_to_mp_obj_cside(out);
+    } else {
+        return MP_OBJ_STOP_ITERATION;
+    }
+}
+
+static mp_obj_t jsproxy_new_it(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
+    assert(sizeof(jsproxy_it_t) <= sizeof(mp_obj_iter_buf_t));
+    mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+    jsproxy_it_t *o = (jsproxy_it_t *)iter_buf;
+    o->base.type = &mp_type_polymorph_iter;
+    o->iternext = jsproxy_it_iternext;
+    o->ref = self->ref;
+    o->cur = 0;
+    o->len = js_get_len(self->ref);
+    return MP_OBJ_FROM_PTR(o);
+}
+
+/******************************************************************************/
+// jsproxy generator
+
+enum {
+    JSOBJ_GEN_STATE_WAITING,
+    JSOBJ_GEN_STATE_COMPLETED,
+    JSOBJ_GEN_STATE_EXHAUSTED,
+};
+
+typedef struct _jsproxy_gen_t {
+    mp_obj_base_t base;
+    mp_obj_t thenable;
+    int state;
+} jsproxy_gen_t;
+
+mp_vm_return_kind_t jsproxy_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) {
+    jsproxy_gen_t *self = MP_OBJ_TO_PTR(self_in);
+    switch (self->state) {
+        case JSOBJ_GEN_STATE_WAITING:
+            self->state = JSOBJ_GEN_STATE_COMPLETED;
+            *ret_val = self->thenable;
+            return MP_VM_RETURN_YIELD;
+
+        case JSOBJ_GEN_STATE_COMPLETED:
+            self->state = JSOBJ_GEN_STATE_EXHAUSTED;
+            *ret_val = send_value;
+            return MP_VM_RETURN_NORMAL;
+
+        case JSOBJ_GEN_STATE_EXHAUSTED:
+        default:
+            // Trying to resume an already stopped generator.
+            // This is an optimised "raise StopIteration(None)".
+            *ret_val = mp_const_none;
+            return MP_VM_RETURN_NORMAL;
+    }
+}
+
+static mp_obj_t jsproxy_gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, bool raise_stop_iteration) {
+    mp_obj_t ret;
+    switch (jsproxy_gen_resume(self_in, send_value, throw_value, &ret)) {
+        case MP_VM_RETURN_NORMAL:
+        default:
+            // A normal return is a StopIteration, either raise it or return
+            // MP_OBJ_STOP_ITERATION as an optimisation.
+            if (ret == mp_const_none) {
+                ret = MP_OBJ_NULL;
+            }
+            if (raise_stop_iteration) {
+                mp_raise_StopIteration(ret);
+            } else {
+                return mp_make_stop_iteration(ret);
+            }
+
+        case MP_VM_RETURN_YIELD:
+            return ret;
+
+        case MP_VM_RETURN_EXCEPTION:
+            nlr_raise(ret);
+    }
+}
+
+static mp_obj_t jsproxy_gen_instance_iternext(mp_obj_t self_in) {
+    return jsproxy_gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL, false);
+}
+
+static mp_obj_t jsproxy_gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
+    return jsproxy_gen_resume_and_raise(self_in, send_value, MP_OBJ_NULL, true);
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(jsproxy_gen_instance_send_obj, jsproxy_gen_instance_send);
+
+static mp_obj_t jsproxy_gen_instance_throw(size_t n_args, const mp_obj_t *args) {
+    // The signature of this function is: throw(type[, value[, traceback]])
+    // CPython will pass all given arguments through the call chain and process them
+    // at the point they are used (native generators will handle them differently to
+    // user-defined generators with a throw() method).  To save passing multiple
+    // values, MicroPython instead does partial processing here to reduce it down to
+    // one argument and passes that through:
+    // - if only args[1] is given, or args[2] is given but is None, args[1] is
+    //   passed through (in the standard case it is an exception class or instance)
+    // - if args[2] is given and not None it is passed through (in the standard
+    //   case it would be an exception instance and args[1] its corresponding class)
+    // - args[3] is always ignored
+
+    mp_obj_t exc = args[1];
+    if (n_args > 2 && args[2] != mp_const_none) {
+        exc = args[2];
+    }
+
+    return jsproxy_gen_resume_and_raise(args[0], mp_const_none, exc, true);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(jsproxy_gen_instance_throw_obj, 2, 4, jsproxy_gen_instance_throw);
+
+static mp_obj_t jsproxy_gen_instance_close(mp_obj_t self_in) {
+    mp_obj_t ret;
+    switch (jsproxy_gen_resume(self_in, mp_const_none, MP_OBJ_FROM_PTR(&mp_const_GeneratorExit_obj), &ret)) {
+        case MP_VM_RETURN_YIELD:
+            mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("generator ignored GeneratorExit"));
+
+        // Swallow GeneratorExit (== successful close), and re-raise any other
+        case MP_VM_RETURN_EXCEPTION:
+            // ret should always be an instance of an exception class
+            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;
+            }
+            nlr_raise(ret);
+
+        default:
+            // The only choice left is MP_VM_RETURN_NORMAL which is successful close
+            return mp_const_none;
+    }
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(jsproxy_gen_instance_close_obj, jsproxy_gen_instance_close);
+
+static const mp_rom_map_elem_t jsproxy_gen_instance_locals_dict_table[] = {
+    { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&jsproxy_gen_instance_close_obj) },
+    { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&jsproxy_gen_instance_send_obj) },
+    { MP_ROM_QSTR(MP_QSTR_throw), MP_ROM_PTR(&jsproxy_gen_instance_throw_obj) },
+};
+static MP_DEFINE_CONST_DICT(jsproxy_gen_instance_locals_dict, jsproxy_gen_instance_locals_dict_table);
+
+MP_DEFINE_CONST_OBJ_TYPE(
+    mp_type_jsproxy_gen,
+    MP_QSTR_generator,
+    MP_TYPE_FLAG_ITER_IS_ITERNEXT,
+    iter, jsproxy_gen_instance_iternext,
+    locals_dict, &jsproxy_gen_instance_locals_dict
+    );
+
+static mp_obj_t jsproxy_new_gen(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
+    assert(sizeof(jsproxy_gen_t) <= sizeof(mp_obj_iter_buf_t));
+    jsproxy_gen_t *o = (jsproxy_gen_t *)iter_buf;
+    o->base.type = &mp_type_jsproxy_gen;
+    o->thenable = self_in;
+    o->state = JSOBJ_GEN_STATE_WAITING;
+    return MP_OBJ_FROM_PTR(o);
+}
+
+/******************************************************************************/
+
+static mp_obj_t jsproxy_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
+    mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
+    if (has_attr(self->ref, "then")) {
+        return jsproxy_new_gen(self_in, iter_buf);
+    } else {
+        return jsproxy_new_it(self_in, iter_buf);
+    }
+}
+
+MP_DEFINE_CONST_OBJ_TYPE(
+    mp_type_jsproxy,
+    MP_QSTR_JsProxy,
+    MP_TYPE_FLAG_ITER_IS_GETITER,
+    print, jsproxy_print,
+    call, jsproxy_call,
+    attr, mp_obj_jsproxy_attr,
+    subscr, jsproxy_subscr,
+    iter, jsproxy_getiter
+    );
+
+mp_obj_t mp_obj_new_jsproxy(int ref) {
+    mp_obj_jsproxy_t *o = mp_obj_malloc(mp_obj_jsproxy_t, &mp_type_jsproxy);
+    o->ref = ref;
+    return MP_OBJ_FROM_PTR(o);
+}
diff --git a/ports/webassembly/objpyproxy.js b/ports/webassembly/objpyproxy.js
new file mode 100644
index 000000000000..9ba06283ea2b
--- /dev/null
+++ b/ports/webassembly/objpyproxy.js
@@ -0,0 +1,214 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+class PyProxy {
+    constructor(ref) {
+        this._ref = ref;
+    }
+
+    // Convert js_obj -- which is possibly a PyProxy -- to a JavaScript object.
+    static toJs(js_obj) {
+        if (!(js_obj instanceof PyProxy)) {
+            return js_obj;
+        }
+
+        const type = Module.ccall(
+            "proxy_c_to_js_get_type",
+            "number",
+            ["number"],
+            [js_obj._ref],
+        );
+
+        if (type === 1 || type === 2) {
+            // List or tuple.
+            const array_ref = Module._malloc(2 * 4);
+            const item = Module._malloc(3 * 4);
+            Module.ccall(
+                "proxy_c_to_js_get_array",
+                "null",
+                ["number", "pointer"],
+                [js_obj._ref, array_ref],
+            );
+            const len = Module.getValue(array_ref, "i32");
+            const items_ptr = Module.getValue(array_ref + 4, "i32");
+            const js_array = [];
+            for (let i = 0; i < len; ++i) {
+                Module.ccall(
+                    "proxy_convert_mp_to_js_obj_cside",
+                    "null",
+                    ["pointer", "pointer"],
+                    [Module.getValue(items_ptr + i * 4, "i32"), item],
+                );
+                const js_item = proxy_convert_mp_to_js_obj_jsside(item);
+                js_array.push(PyProxy.toJs(js_item));
+            }
+            Module._free(array_ref);
+            Module._free(item);
+            return js_array;
+        }
+
+        if (type === 3) {
+            // Dict.
+            const map_ref = Module._malloc(2 * 4);
+            const item = Module._malloc(3 * 4);
+            Module.ccall(
+                "proxy_c_to_js_get_dict",
+                "null",
+                ["number", "pointer"],
+                [js_obj._ref, map_ref],
+            );
+            const alloc = Module.getValue(map_ref, "i32");
+            const table_ptr = Module.getValue(map_ref + 4, "i32");
+            const js_dict = {};
+            for (let i = 0; i < alloc; ++i) {
+                const mp_key = Module.getValue(table_ptr + i * 8, "i32");
+                if (mp_key > 8) {
+                    // Convert key to JS object.
+                    Module.ccall(
+                        "proxy_convert_mp_to_js_obj_cside",
+                        "null",
+                        ["pointer", "pointer"],
+                        [mp_key, item],
+                    );
+                    const js_key = proxy_convert_mp_to_js_obj_jsside(item);
+
+                    // Convert value to JS object.
+                    const mp_value = Module.getValue(
+                        table_ptr + i * 8 + 4,
+                        "i32",
+                    );
+                    Module.ccall(
+                        "proxy_convert_mp_to_js_obj_cside",
+                        "null",
+                        ["pointer", "pointer"],
+                        [mp_value, item],
+                    );
+                    const js_value = proxy_convert_mp_to_js_obj_jsside(item);
+
+                    // Populate JS dict.
+                    js_dict[js_key] = PyProxy.toJs(js_value);
+                }
+            }
+            Module._free(map_ref);
+            Module._free(item);
+            return js_dict;
+        }
+
+        // Cannot convert to JS, leave as a PyProxy.
+        return js_obj;
+    }
+}
+
+// This handler's goal is to allow minimal introspection
+// of Python references from the JS world/utilities.
+const py_proxy_handler = {
+    isExtensible() {
+        return true;
+    },
+    ownKeys(target) {
+        const value = Module._malloc(3 * 4);
+        Module.ccall(
+            "proxy_c_to_js_dir",
+            "null",
+            ["number", "pointer"],
+            [target._ref, value],
+        );
+        const dir = proxy_convert_mp_to_js_obj_jsside_with_free(value);
+        return PyProxy.toJs(dir).filter((attr) => !attr.startsWith("__"));
+    },
+    getOwnPropertyDescriptor(target, prop) {
+        return {
+            value: target[prop],
+            enumerable: true,
+            writable: true,
+            configurable: true,
+        };
+    },
+    has(target, prop) {
+        return Module.ccall(
+            "proxy_c_to_js_has_attr",
+            "number",
+            ["number", "string"],
+            [target._ref, prop],
+        );
+    },
+    get(target, prop) {
+        if (prop === "_ref") {
+            return target._ref;
+        }
+        if (prop === "then") {
+            return null;
+        }
+        const value = Module._malloc(3 * 4);
+        Module.ccall(
+            "proxy_c_to_js_lookup_attr",
+            "number",
+            ["number", "string", "pointer"],
+            [target._ref, prop, value],
+        );
+        return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+    },
+    set(target, prop, value) {
+        const value_conv = Module._malloc(3 * 4);
+        proxy_convert_js_to_mp_obj_jsside(value, value_conv);
+        const ret = Module.ccall(
+            "proxy_c_to_js_store_attr",
+            "number",
+            ["number", "string", "number"],
+            [target._ref, prop, value_conv],
+        );
+        Module._free(value_conv);
+        return ret;
+    },
+    deleteProperty(target, prop) {
+        return Module.ccall(
+            "proxy_c_to_js_delete_attr",
+            "number",
+            ["number", "string"],
+            [target._ref, prop],
+        );
+    },
+};
+
+// PyProxy of a Python generator, that implements the thenable interface.
+class PyProxyThenable {
+    constructor(ref) {
+        this._ref = ref;
+    }
+
+    then(resolve, reject) {
+        const values = Module._malloc(3 * 3 * 4);
+        proxy_convert_js_to_mp_obj_jsside(resolve, values + 3 * 4);
+        proxy_convert_js_to_mp_obj_jsside(reject, values + 2 * 3 * 4);
+        Module.ccall(
+            "proxy_c_to_js_resume",
+            "null",
+            ["number", "pointer"],
+            [this._ref, values],
+        );
+        return proxy_convert_mp_to_js_obj_jsside_with_free(values);
+    }
+}
diff --git a/ports/webassembly/proxy_c.c b/ports/webassembly/proxy_c.c
new file mode 100644
index 000000000000..1e4573ce0ba5
--- /dev/null
+++ b/ports/webassembly/proxy_c.c
@@ -0,0 +1,360 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include 
+#include 
+
+#include "emscripten.h"
+#include "py/builtin.h"
+#include "py/runtime.h"
+#include "proxy_c.h"
+
+// These constants should match the constants in proxy_js.js.
+
+enum {
+    PROXY_KIND_MP_EXCEPTION = -1,
+    PROXY_KIND_MP_NULL = 0,
+    PROXY_KIND_MP_NONE = 1,
+    PROXY_KIND_MP_BOOL = 2,
+    PROXY_KIND_MP_INT = 3,
+    PROXY_KIND_MP_FLOAT = 4,
+    PROXY_KIND_MP_STR = 5,
+    PROXY_KIND_MP_CALLABLE = 6,
+    PROXY_KIND_MP_GENERATOR = 7,
+    PROXY_KIND_MP_OBJECT = 8,
+    PROXY_KIND_MP_JSPROXY = 9,
+};
+
+enum {
+    PROXY_KIND_JS_NULL = 1,
+    PROXY_KIND_JS_BOOLEAN = 2,
+    PROXY_KIND_JS_INTEGER = 3,
+    PROXY_KIND_JS_DOUBLE = 4,
+    PROXY_KIND_JS_STRING = 5,
+    PROXY_KIND_JS_OBJECT = 6,
+    PROXY_KIND_JS_PYPROXY = 7,
+};
+
+void proxy_c_init(void) {
+    MP_STATE_PORT(proxy_c_ref) = mp_obj_new_list(0, NULL);
+    mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), MP_OBJ_NULL);
+}
+
+MP_REGISTER_ROOT_POINTER(mp_obj_t proxy_c_ref);
+
+static inline mp_obj_t proxy_c_get_obj(uint32_t c_ref) {
+    return ((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->items[c_ref];
+}
+
+mp_obj_t proxy_convert_js_to_mp_obj_cside(uint32_t *value) {
+    if (value[0] == PROXY_KIND_JS_NULL) {
+        return mp_const_none;
+    } else if (value[0] == PROXY_KIND_JS_BOOLEAN) {
+        return mp_obj_new_bool(value[1]);
+    } else if (value[0] == PROXY_KIND_JS_INTEGER) {
+        return mp_obj_new_int(value[1]);
+    } else if (value[0] == PROXY_KIND_JS_DOUBLE) {
+        return mp_obj_new_float_from_d(*(double *)&value[1]);
+    } else if (value[0] == PROXY_KIND_JS_STRING) {
+        mp_obj_t s = mp_obj_new_str((void *)value[2], value[1]);
+        free((void *)value[2]);
+        return s;
+    } else if (value[0] == PROXY_KIND_JS_PYPROXY) {
+        return proxy_c_get_obj(value[1]);
+    } else {
+        // PROXY_KIND_JS_OBJECT
+        return mp_obj_new_jsproxy(value[1]);
+    }
+}
+
+void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out) {
+    uint32_t kind;
+    if (obj == MP_OBJ_NULL) {
+        kind = PROXY_KIND_MP_NULL;
+    } else if (obj == mp_const_none) {
+        kind = PROXY_KIND_MP_NONE;
+    } else if (mp_obj_is_bool(obj)) {
+        kind = PROXY_KIND_MP_BOOL;
+        out[1] = mp_obj_is_true(obj);
+    } else if (mp_obj_is_int(obj)) {
+        kind = PROXY_KIND_MP_INT;
+        out[1] = mp_obj_get_int_truncated(obj); // TODO support big int
+    } else if (mp_obj_is_float(obj)) {
+        kind = PROXY_KIND_MP_FLOAT;
+        *(double *)&out[1] = mp_obj_get_float(obj);
+    } else if (mp_obj_is_str(obj)) {
+        kind = PROXY_KIND_MP_STR;
+        size_t len;
+        const char *str = mp_obj_str_get_data(obj, &len);
+        out[1] = len;
+        out[2] = (uintptr_t)str;
+    } else if (mp_obj_is_jsproxy(obj)) {
+        kind = PROXY_KIND_MP_JSPROXY;
+        out[1] = mp_obj_jsproxy_get_ref(obj);
+    } else {
+        if (mp_obj_is_callable(obj)) {
+            kind = PROXY_KIND_MP_CALLABLE;
+        } else if (mp_obj_is_type(obj, &mp_type_gen_instance)) {
+            kind = PROXY_KIND_MP_GENERATOR;
+        } else {
+            kind = PROXY_KIND_MP_OBJECT;
+        }
+        size_t id = ((mp_obj_list_t *)MP_OBJ_TO_PTR(MP_STATE_PORT(proxy_c_ref)))->len;
+        mp_obj_list_append(MP_STATE_PORT(proxy_c_ref), obj);
+        out[1] = id;
+    }
+    out[0] = kind;
+}
+
+void proxy_convert_mp_to_js_exc_cside(void *exc, uint32_t *out) {
+    out[0] = PROXY_KIND_MP_EXCEPTION;
+    vstr_t vstr;
+    mp_print_t print;
+    vstr_init_print(&vstr, 64, &print);
+    vstr_add_str(&vstr, qstr_str(mp_obj_get_type(MP_OBJ_FROM_PTR(exc))->name));
+    vstr_add_char(&vstr, '\x04');
+    mp_obj_print_exception(&print, MP_OBJ_FROM_PTR(exc));
+    char *s = malloc(vstr_len(&vstr) + 1);
+    memcpy(s, vstr_str(&vstr), vstr_len(&vstr));
+    out[1] = vstr_len(&vstr);
+    out[2] = (uintptr_t)s;
+    vstr_clear(&vstr);
+}
+
+void proxy_c_to_js_call(uint32_t c_ref, uint32_t n_args, uint32_t *args_value, uint32_t *out) {
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t args[4] = { mp_const_none, mp_const_none, mp_const_none, mp_const_none };
+        for (size_t i = 0; i < n_args; ++i) {
+            args[i] = proxy_convert_js_to_mp_obj_cside(args_value + i * 3);
+        }
+        mp_obj_t obj = proxy_c_get_obj(c_ref);
+        mp_obj_t member = mp_call_function_n_kw(obj, n_args, 0, args);
+        nlr_pop();
+        proxy_convert_mp_to_js_obj_cside(member, out);
+    } else {
+        // uncaught exception
+        proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+    }
+}
+
+void proxy_c_to_js_dir(uint32_t c_ref, uint32_t *out) {
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t obj = proxy_c_get_obj(c_ref);
+        mp_obj_t dir;
+        if (mp_obj_is_dict_or_ordereddict(obj)) {
+            mp_map_t *map = mp_obj_dict_get_map(obj);
+            dir = mp_obj_new_list(0, NULL);
+            for (size_t i = 0; i < map->alloc; i++) {
+                if (mp_map_slot_is_filled(map, i)) {
+                    mp_obj_list_append(dir, map->table[i].key);
+                }
+            }
+        } else {
+            mp_obj_t args[1] = { obj };
+            dir = mp_builtin_dir_obj.fun.var(1, args);
+        }
+        nlr_pop();
+        return proxy_convert_mp_to_js_obj_cside(dir, out);
+    } else {
+        // uncaught exception
+        return proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+    }
+}
+
+bool proxy_c_to_js_has_attr(uint32_t c_ref, const char *attr_in) {
+    mp_obj_t obj = proxy_c_get_obj(c_ref);
+    qstr attr = qstr_from_str(attr_in);
+    if (mp_obj_is_dict_or_ordereddict(obj)) {
+        mp_map_t *map = mp_obj_dict_get_map(obj);
+        mp_map_elem_t *elem = mp_map_lookup(map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
+        return elem != NULL;
+    } else {
+        mp_obj_t dest[2];
+        mp_load_method_protected(obj, attr, dest, true);
+        if (dest[0] != MP_OBJ_NULL) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void proxy_c_to_js_lookup_attr(uint32_t c_ref, const char *attr_in, uint32_t *out) {
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t obj = proxy_c_get_obj(c_ref);
+        qstr attr = qstr_from_str(attr_in);
+        mp_obj_t member;
+        if (mp_obj_is_dict_or_ordereddict(obj)) {
+            member = mp_obj_dict_get(obj, MP_OBJ_NEW_QSTR(attr));
+        } else {
+            member = mp_load_attr(obj, attr);
+        }
+        nlr_pop();
+        return proxy_convert_mp_to_js_obj_cside(member, out);
+    } else {
+        // uncaught exception
+        return proxy_convert_mp_to_js_exc_cside(nlr.ret_val, out);
+    }
+}
+
+static bool proxy_c_to_js_store_helper(uint32_t c_ref, const char *attr_in, mp_obj_t value) {
+    mp_obj_t obj = proxy_c_get_obj(c_ref);
+    qstr attr = qstr_from_str(attr_in);
+
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        if (mp_obj_is_dict_or_ordereddict(obj)) {
+            if (value == MP_OBJ_NULL) {
+                mp_obj_dict_delete(obj, MP_OBJ_NEW_QSTR(attr));
+            } else {
+                mp_obj_dict_store(obj, MP_OBJ_NEW_QSTR(attr), value);
+            }
+        } else {
+            mp_store_attr(obj, attr, value);
+        }
+        nlr_pop();
+        return true;
+    } else {
+        // uncaught exception
+        return false;
+    }
+}
+
+bool proxy_c_to_js_store_attr(uint32_t c_ref, const char *attr_in, uint32_t *value_in) {
+    mp_obj_t value = proxy_convert_js_to_mp_obj_cside(value_in);
+    return proxy_c_to_js_store_helper(c_ref, attr_in, value);
+}
+
+bool proxy_c_to_js_delete_attr(uint32_t c_ref, const char *attr_in) {
+    return proxy_c_to_js_store_helper(c_ref, attr_in, MP_OBJ_NULL);
+}
+
+uint32_t proxy_c_to_js_get_type(uint32_t c_ref) {
+    mp_obj_t obj = proxy_c_get_obj(c_ref);
+    const mp_obj_type_t *type = mp_obj_get_type(obj);
+    if (type == &mp_type_tuple) {
+        return 1;
+    } else if (type == &mp_type_list) {
+        return 2;
+    } else if (type == &mp_type_dict) {
+        return 3;
+    } else {
+        return 4;
+    }
+}
+
+void proxy_c_to_js_get_array(uint32_t c_ref, uint32_t *out) {
+    mp_obj_t obj = proxy_c_get_obj(c_ref);
+    size_t len;
+    mp_obj_t *items;
+    mp_obj_get_array(obj, &len, &items);
+    out[0] = len;
+    out[1] = (uintptr_t)items;
+}
+
+void proxy_c_to_js_get_dict(uint32_t c_ref, uint32_t *out) {
+    mp_obj_t obj = proxy_c_get_obj(c_ref);
+    mp_map_t *map = mp_obj_dict_get_map(obj);
+    out[0] = map->alloc;
+    out[1] = (uintptr_t)map->table;
+}
+
+/******************************************************************************/
+// Bridge Python generator to JavaScript thenable.
+
+static const mp_obj_fun_builtin_var_t resume_obj;
+
+EM_JS(void, js_then_resolve, (uint32_t * resolve, uint32_t * reject), {
+    const resolve_js = proxy_convert_mp_to_js_obj_jsside(resolve);
+    const reject_js = proxy_convert_mp_to_js_obj_jsside(reject);
+    resolve_js(null);
+});
+
+EM_JS(void, js_then_reject, (uint32_t * resolve, uint32_t * reject), {
+    const resolve_js = proxy_convert_mp_to_js_obj_jsside(resolve);
+    const reject_js = proxy_convert_mp_to_js_obj_jsside(reject);
+    reject_js(null);
+});
+
+// *FORMAT-OFF*
+EM_JS(void, js_then_continue, (int jsref, uint32_t * py_resume, uint32_t * resolve, uint32_t * reject, uint32_t * out), {
+    const py_resume_js = proxy_convert_mp_to_js_obj_jsside(py_resume);
+    const resolve_js = proxy_convert_mp_to_js_obj_jsside(resolve);
+    const reject_js = proxy_convert_mp_to_js_obj_jsside(reject);
+    const ret = proxy_js_ref[jsref].then((x) => {py_resume_js(x, resolve_js, reject_js);}, reject_js);
+    proxy_convert_js_to_mp_obj_jsside(ret, out);
+});
+// *FORMAT-ON*
+
+static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t value, mp_obj_t resolve, mp_obj_t reject) {
+    mp_obj_t ret_value;
+    mp_vm_return_kind_t ret_kind = mp_resume(self_in, value, MP_OBJ_NULL, &ret_value);
+
+    uint32_t out_resolve[PVN];
+    uint32_t out_reject[PVN];
+    proxy_convert_mp_to_js_obj_cside(resolve, out_resolve);
+    proxy_convert_mp_to_js_obj_cside(reject, out_reject);
+
+    if (ret_kind == MP_VM_RETURN_NORMAL) {
+        js_then_resolve(out_resolve, out_reject);
+        return mp_const_none;
+    } else if (ret_kind == MP_VM_RETURN_YIELD) {
+        // ret_value should be a JS thenable
+        mp_obj_t py_resume = mp_obj_new_bound_meth(MP_OBJ_FROM_PTR(&resume_obj), self_in);
+        int ref = mp_obj_jsproxy_get_ref(ret_value);
+        uint32_t out_py_resume[PVN];
+        proxy_convert_mp_to_js_obj_cside(py_resume, out_py_resume);
+        uint32_t out[PVN];
+        js_then_continue(ref, out_py_resume, out_resolve, out_reject, out);
+        return proxy_convert_js_to_mp_obj_cside(out);
+    } else {
+        // MP_VM_RETURN_EXCEPTION;
+        js_then_reject(out_resolve, out_reject);
+        nlr_raise(ret_value);
+    }
+}
+
+static mp_obj_t resume_fun(size_t n_args, const mp_obj_t *args) {
+    return proxy_resume_execute(args[0], args[1], args[2], args[3]);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(resume_obj, 4, 4, resume_fun);
+
+void proxy_c_to_js_resume(uint32_t c_ref, uint32_t *args) {
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t obj = proxy_c_get_obj(c_ref);
+        mp_obj_t resolve = proxy_convert_js_to_mp_obj_cside(args + 1 * 3);
+        mp_obj_t reject = proxy_convert_js_to_mp_obj_cside(args + 2 * 3);
+        mp_obj_t ret = proxy_resume_execute(obj, mp_const_none, resolve, reject);
+        nlr_pop();
+        return proxy_convert_mp_to_js_obj_cside(ret, args);
+    } else {
+        // uncaught exception
+        return proxy_convert_mp_to_js_exc_cside(nlr.ret_val, args);
+    }
+}
diff --git a/ports/webassembly/proxy_c.h b/ports/webassembly/proxy_c.h
new file mode 100644
index 000000000000..3e68d2504958
--- /dev/null
+++ b/ports/webassembly/proxy_c.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef MICROPY_INCLUDED_WEBASSEMBLY_PROXY_C_H
+#define MICROPY_INCLUDED_WEBASSEMBLY_PROXY_C_H
+
+#include "py/obj.h"
+
+// proxy value number of items
+#define PVN (3)
+
+typedef struct _mp_obj_jsproxy_t {
+    mp_obj_base_t base;
+    int ref;
+} mp_obj_jsproxy_t;
+
+extern const mp_obj_type_t mp_type_jsproxy;
+
+void proxy_c_init(void);
+mp_obj_t proxy_convert_js_to_mp_obj_cside(uint32_t *value);
+void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out);
+void proxy_convert_mp_to_js_exc_cside(void *exc, uint32_t *out);
+
+mp_obj_t mp_obj_new_jsproxy(int ref);
+void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
+
+static inline bool mp_obj_is_jsproxy(mp_obj_t o) {
+    return mp_obj_get_type(o) == &mp_type_jsproxy;
+}
+
+static inline int mp_obj_jsproxy_get_ref(mp_obj_t o) {
+    mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(o);
+    return self->ref;
+}
+
+#endif // MICROPY_INCLUDED_WEBASSEMBLY_PROXY_C_H
diff --git a/ports/webassembly/proxy_js.js b/ports/webassembly/proxy_js.js
new file mode 100644
index 000000000000..7a0a1bbe8932
--- /dev/null
+++ b/ports/webassembly/proxy_js.js
@@ -0,0 +1,228 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2024 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// These constants should match the constants in proxy_c.c.
+
+const PROXY_KIND_MP_EXCEPTION = -1;
+const PROXY_KIND_MP_NULL = 0;
+const PROXY_KIND_MP_NONE = 1;
+const PROXY_KIND_MP_BOOL = 2;
+const PROXY_KIND_MP_INT = 3;
+const PROXY_KIND_MP_FLOAT = 4;
+const PROXY_KIND_MP_STR = 5;
+const PROXY_KIND_MP_CALLABLE = 6;
+const PROXY_KIND_MP_GENERATOR = 7;
+const PROXY_KIND_MP_OBJECT = 8;
+const PROXY_KIND_MP_JSPROXY = 9;
+
+const PROXY_KIND_JS_NULL = 1;
+const PROXY_KIND_JS_BOOLEAN = 2;
+const PROXY_KIND_JS_INTEGER = 3;
+const PROXY_KIND_JS_DOUBLE = 4;
+const PROXY_KIND_JS_STRING = 5;
+const PROXY_KIND_JS_OBJECT = 6;
+const PROXY_KIND_JS_PYPROXY = 7;
+
+class PythonError extends Error {
+    constructor(exc_type, exc_details) {
+        super(exc_details);
+        this.name = "PythonError";
+        this.type = exc_type;
+    }
+}
+
+function proxy_js_init() {
+    globalThis.proxy_js_ref = [globalThis];
+}
+
+function proxy_call_python(target, argumentsList) {
+    let args = 0;
+
+    // Strip trailing "undefined" arguments.
+    while (
+        argumentsList.length > 0 &&
+        argumentsList[argumentsList.length - 1] === undefined
+    ) {
+        argumentsList.pop();
+    }
+
+    if (argumentsList.length > 0) {
+        // TODO use stackAlloc/stackRestore?
+        args = Module._malloc(argumentsList.length * 3 * 4);
+        for (const i in argumentsList) {
+            proxy_convert_js_to_mp_obj_jsside(
+                argumentsList[i],
+                args + i * 3 * 4,
+            );
+        }
+    }
+    const value = Module._malloc(3 * 4);
+    Module.ccall(
+        "proxy_c_to_js_call",
+        "null",
+        ["number", "number", "number", "pointer"],
+        [target, argumentsList.length, args, value],
+    );
+    if (argumentsList.length > 0) {
+        Module._free(args);
+    }
+    return proxy_convert_mp_to_js_obj_jsside_with_free(value);
+}
+
+function proxy_convert_js_to_mp_obj_jsside(js_obj, out) {
+    let kind;
+    if (js_obj === null) {
+        kind = PROXY_KIND_JS_NULL;
+    } else if (typeof js_obj === "boolean") {
+        kind = PROXY_KIND_JS_BOOLEAN;
+        Module.setValue(out + 4, js_obj, "i32");
+    } else if (typeof js_obj === "number") {
+        if (Number.isInteger(js_obj)) {
+            kind = PROXY_KIND_JS_INTEGER;
+            Module.setValue(out + 4, js_obj, "i32");
+        } else {
+            kind = PROXY_KIND_JS_DOUBLE;
+            // double must be stored to an address that's a multiple of 8
+            const temp = (out + 4) & ~7;
+            Module.setValue(temp, js_obj, "double");
+            const double_lo = Module.getValue(temp, "i32");
+            const double_hi = Module.getValue(temp + 4, "i32");
+            Module.setValue(out + 4, double_lo, "i32");
+            Module.setValue(out + 8, double_hi, "i32");
+        }
+    } else if (typeof js_obj === "string") {
+        kind = PROXY_KIND_JS_STRING;
+        const len = Module.lengthBytesUTF8(js_obj);
+        const buf = Module._malloc(len + 1);
+        Module.stringToUTF8(js_obj, buf, len + 1);
+        Module.setValue(out + 4, len, "i32");
+        Module.setValue(out + 8, buf, "i32");
+    } else if (js_obj instanceof PyProxy) {
+        kind = PROXY_KIND_JS_PYPROXY;
+        Module.setValue(out + 4, js_obj._ref, "i32");
+    } else if (js_obj instanceof PyProxyThenable) {
+        kind = PROXY_KIND_JS_PYPROXY;
+        Module.setValue(out + 4, js_obj._ref, "i32");
+    } else {
+        kind = PROXY_KIND_JS_OBJECT;
+        const id = proxy_js_ref.length;
+        proxy_js_ref[id] = js_obj;
+        Module.setValue(out + 4, id, "i32");
+    }
+    Module.setValue(out + 0, kind, "i32");
+}
+
+function proxy_convert_js_to_mp_obj_jsside_force_double_proxy(js_obj, out) {
+    if (js_obj instanceof PyProxy) {
+        const kind = PROXY_KIND_JS_OBJECT;
+        const id = proxy_js_ref.length;
+        proxy_js_ref[id] = js_obj;
+        Module.setValue(out + 4, id, "i32");
+        Module.setValue(out + 0, kind, "i32");
+    } else {
+        proxy_convert_js_to_mp_obj_jsside(js_obj, out);
+    }
+}
+
+function proxy_convert_mp_to_js_obj_jsside(value) {
+    const kind = Module.getValue(value, "i32");
+    let obj;
+    if (kind === PROXY_KIND_MP_EXCEPTION) {
+        // Exception
+        const str_len = Module.getValue(value + 4, "i32");
+        const str_ptr = Module.getValue(value + 8, "i32");
+        const str = Module.UTF8ToString(str_ptr, str_len);
+        Module._free(str_ptr);
+        const str_split = str.split("\x04");
+        throw new PythonError(str_split[0], str_split[1]);
+    }
+    if (kind === PROXY_KIND_MP_NULL) {
+        // MP_OBJ_NULL
+        throw new Error("NULL object");
+    }
+    if (kind === PROXY_KIND_MP_NONE) {
+        // None
+        obj = null;
+    } else if (kind === PROXY_KIND_MP_BOOL) {
+        // bool
+        obj = Module.getValue(value + 4, "i32") ? true : false;
+    } else if (kind === PROXY_KIND_MP_INT) {
+        // int
+        obj = Module.getValue(value + 4, "i32");
+    } else if (kind === PROXY_KIND_MP_FLOAT) {
+        // float
+        // double must be loaded from an address that's a multiple of 8
+        const temp = (value + 4) & ~7;
+        const double_lo = Module.getValue(value + 4, "i32");
+        const double_hi = Module.getValue(value + 8, "i32");
+        Module.setValue(temp, double_lo, "i32");
+        Module.setValue(temp + 4, double_hi, "i32");
+        obj = Module.getValue(temp, "double");
+    } else if (kind === PROXY_KIND_MP_STR) {
+        // str
+        const str_len = Module.getValue(value + 4, "i32");
+        const str_ptr = Module.getValue(value + 8, "i32");
+        obj = Module.UTF8ToString(str_ptr, str_len);
+    } else if (kind === PROXY_KIND_MP_JSPROXY) {
+        // js proxy
+        const id = Module.getValue(value + 4, "i32");
+        obj = proxy_js_ref[id];
+    } else {
+        // obj
+        const id = Module.getValue(value + 4, "i32");
+        if (kind === PROXY_KIND_MP_CALLABLE) {
+            obj = (...args) => {
+                return proxy_call_python(id, args);
+            };
+        } else if (kind === PROXY_KIND_MP_GENERATOR) {
+            obj = new PyProxyThenable(id);
+        } else {
+            // PROXY_KIND_MP_OBJECT
+            const target = new PyProxy(id);
+            obj = new Proxy(target, py_proxy_handler);
+        }
+    }
+    return obj;
+}
+
+function proxy_convert_mp_to_js_obj_jsside_with_free(value) {
+    const ret = proxy_convert_mp_to_js_obj_jsside(value);
+    Module._free(value);
+    return ret;
+}
+
+function python_index_semantics(target, index_in) {
+    let index = index_in;
+    if (typeof index === "number") {
+        if (index < 0) {
+            index += target.length;
+        }
+        if (index < 0 || index >= target.length) {
+            throw new PythonError("IndexError", "index out of range");
+        }
+    }
+    return index;
+}
diff --git a/ports/webassembly/qstrdefsport.h b/ports/webassembly/qstrdefsport.h
index 00d3e2ae3c55..472d05f4375f 100644
--- a/ports/webassembly/qstrdefsport.h
+++ b/ports/webassembly/qstrdefsport.h
@@ -1,2 +1,3 @@
 // qstrs specific to this port
 // *FORMAT-OFF*
+Q(/lib)
diff --git a/ports/webassembly/variants/pyscript/manifest.py b/ports/webassembly/variants/pyscript/manifest.py
new file mode 100644
index 000000000000..0646e1d897f0
--- /dev/null
+++ b/ports/webassembly/variants/pyscript/manifest.py
@@ -0,0 +1,27 @@
+require("abc")
+require("base64")
+require("collections")
+require("collections-defaultdict")
+require("copy")
+require("datetime")
+require("fnmatch")
+require("functools")
+require("gzip")
+require("hmac")
+require("html")
+require("inspect")
+require("io")
+require("itertools")
+require("locale")
+require("logging")
+require("operator")
+require("os")
+require("os-path")
+require("pathlib")
+require("stat")
+require("tarfile")
+require("tarfile-write")
+require("time")
+require("unittest")
+require("uu")
+require("zlib")
diff --git a/ports/webassembly/variants/pyscript/mpconfigvariant.h b/ports/webassembly/variants/pyscript/mpconfigvariant.h
new file mode 100644
index 000000000000..ed8e81280353
--- /dev/null
+++ b/ports/webassembly/variants/pyscript/mpconfigvariant.h
@@ -0,0 +1,3 @@
+#define MICROPY_CONFIG_ROM_LEVEL                (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES)
+#define MICROPY_GC_SPLIT_HEAP                   (1)
+#define MICROPY_GC_SPLIT_HEAP_AUTO              (1)
diff --git a/ports/webassembly/variants/pyscript/mpconfigvariant.mk b/ports/webassembly/variants/pyscript/mpconfigvariant.mk
new file mode 100644
index 000000000000..016b96a99af0
--- /dev/null
+++ b/ports/webassembly/variants/pyscript/mpconfigvariant.mk
@@ -0,0 +1,3 @@
+JSFLAGS += -s ALLOW_MEMORY_GROWTH
+
+FROZEN_MANIFEST ?= variants/pyscript/manifest.py
diff --git a/ports/webassembly/variants/standard/mpconfigvariant.h b/ports/webassembly/variants/standard/mpconfigvariant.h
new file mode 100644
index 000000000000..7be62ea7f4f3
--- /dev/null
+++ b/ports/webassembly/variants/standard/mpconfigvariant.h
@@ -0,0 +1 @@
+#define MICROPY_VARIANT_ENABLE_JS_HOOK (1)
diff --git a/ports/webassembly/variants/standard/mpconfigvariant.mk b/ports/webassembly/variants/standard/mpconfigvariant.mk
new file mode 100644
index 000000000000..62ee161907de
--- /dev/null
+++ b/ports/webassembly/variants/standard/mpconfigvariant.mk
@@ -0,0 +1 @@
+JSFLAGS += -s ASYNCIFY
diff --git a/ports/webassembly/wrapper.js b/ports/webassembly/wrapper.js
deleted file mode 100644
index e63abfffe4ae..000000000000
--- a/ports/webassembly/wrapper.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * This file is part of the MicroPython project, http://micropython.org/
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2017, 2018 Rami Ali
- *
- * 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.
- */
-
-var Module = {};
-
-var mainProgram = function()
-{
-  mp_js_init = Module.cwrap('mp_js_init', 'null', ['number']);
-  mp_js_do_str = Module.cwrap('mp_js_do_str', 'number', ['string'], {async: true});
-  mp_js_init_repl = Module.cwrap('mp_js_init_repl', 'null', ['null']);
-  mp_js_process_char = Module.cwrap('mp_js_process_char', 'number', ['number'], {async: true});
-
-  MP_JS_EPOCH = Date.now();
-
-  if (typeof window === 'undefined' && require.main === module) {
-      var fs = require('fs');
-      var heap_size = 128 * 1024;
-      var contents = '';
-      var repl = true;
-
-      for (var i = 0; i < process.argv.length; i++) {
-          if (process.argv[i] === '-X' && i < process.argv.length - 1) {
-              if (process.argv[i + 1].includes('heapsize=')) {
-                  heap_size = parseInt(process.argv[i + 1].split('heapsize=')[1]);
-                  if (process.argv[i + 1].substr(-1).toLowerCase() === 'k') {
-                      heap_size *= 1024;
-                  } else if (process.argv[i + 1].substr(-1).toLowerCase() === 'm') {
-                      heap_size *= 1024 * 1024;
-                  }
-              }
-          } else if (process.argv[i].includes('.py')) {
-              contents += fs.readFileSync(process.argv[i], 'utf8');
-              repl = false;;
-          }
-      }
-
-      if (process.stdin.isTTY === false) {
-          contents = fs.readFileSync(0, 'utf8');
-          repl = 0;
-      }
-
-      mp_js_init(heap_size);
-
-      if (repl) {
-          mp_js_init_repl();
-          process.stdin.setRawMode(true);
-          process.stdin.on('data', function (data) {
-              for (var i = 0; i < data.length; i++) {
-                  mp_js_process_char(data[i]).then(result => {
-                      if (result) {
-                          process.exit()
-                      }
-                  })
-              }
-          });
-      } else {
-          mp_js_do_str(contents).then(exitCode => {
-              process.exitCode = exitCode
-          })
-      }
-  }
-}
-
-Module["onRuntimeInitialized"] = mainProgram;
diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h
index dee5568c6225..55e44c6f5c8c 100644
--- a/ports/windows/mpconfigport.h
+++ b/ports/windows/mpconfigport.h
@@ -117,6 +117,8 @@
 #define MICROPY_PY_SYS_STDFILES     (1)
 #define MICROPY_PY_SYS_EXC_INFO     (1)
 #define MICROPY_PY_COLLECTIONS_DEQUE (1)
+#define MICROPY_PY_COLLECTIONS_DEQUE_ITER (1)
+#define MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR (1)
 #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1)
 #ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS
 #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1)
diff --git a/py/asmarm.c b/py/asmarm.c
index cd346949eb0d..600649070125 100644
--- a/py/asmarm.c
+++ b/py/asmarm.c
@@ -77,6 +77,11 @@ static uint asm_arm_op_mvn_imm(uint rd, uint imm) {
     return 0x3e00000 | (rd << 12) | imm;
 }
 
+static uint asm_arm_op_mvn_reg(uint rd, uint rm) {
+    // mvn rd, rm
+    return 0x1e00000 | (rd << 12) | rm;
+}
+
 static uint asm_arm_op_add_imm(uint rd, uint rn, uint imm) {
     // add rd, rn, #imm
     return 0x2800000 | (rn << 16) | (rd << 12) | (imm & 0xFF);
@@ -97,6 +102,11 @@ static uint asm_arm_op_sub_reg(uint rd, uint rn, uint rm) {
     return 0x0400000 | (rn << 16) | (rd << 12) | rm;
 }
 
+static uint asm_arm_op_rsb_imm(uint rd, uint rn, uint imm) {
+    // rsb rd, rn, #imm
+    return 0x2600000 | (rn << 16) | (rd << 12) | (imm & 0xFF);
+}
+
 static uint asm_arm_op_mul_reg(uint rd, uint rm, uint rs) {
     // mul rd, rm, rs
     assert(rd != rm);
@@ -228,11 +238,23 @@ void asm_arm_setcc_reg(asm_arm_t *as, uint rd, uint cond) {
     emit(as, asm_arm_op_mov_imm(rd, 0) | (cond ^ (1 << 28))); // mov!COND rd, #0
 }
 
+void asm_arm_mvn_reg_reg(asm_arm_t *as, uint rd, uint rm) {
+    // mvn rd, rm
+    // computes: rd := ~rm
+    emit_al(as, asm_arm_op_mvn_reg(rd, rm));
+}
+
 void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) {
     // add rd, rn, rm
     emit_al(as, asm_arm_op_add_reg(rd, rn, rm));
 }
 
+void asm_arm_rsb_reg_reg_imm(asm_arm_t *as, uint rd, uint rn, uint imm) {
+    // rsb rd, rn, #imm
+    // computes: rd := #imm - rn
+    emit_al(as, asm_arm_op_rsb_imm(rd, rn, imm));
+}
+
 void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) {
     // sub rd, rn, rm
     emit_al(as, asm_arm_op_sub_reg(rd, rn, rm));
diff --git a/py/asmarm.h b/py/asmarm.h
index 81c3f7b1cf41..4a4253aef687 100644
--- a/py/asmarm.h
+++ b/py/asmarm.h
@@ -94,8 +94,10 @@ void asm_arm_cmp_reg_i8(asm_arm_t *as, uint rd, int imm);
 void asm_arm_cmp_reg_reg(asm_arm_t *as, uint rd, uint rn);
 
 // arithmetic
+void asm_arm_mvn_reg_reg(asm_arm_t *as, uint rd, uint rm);
 void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm);
 void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm);
+void asm_arm_rsb_reg_reg_imm(asm_arm_t *as, uint rd, uint rn, uint imm);
 void asm_arm_mul_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm);
 void asm_arm_and_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm);
 void asm_arm_eor_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm);
@@ -188,6 +190,8 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src);
 #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_arm_mov_reg_local_addr((as), (reg_dest), (local_num))
 #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_arm_mov_reg_pcrel((as), (reg_dest), (label))
 
+#define ASM_NOT_REG(as, reg_dest) asm_arm_mvn_reg_reg((as), (reg_dest), (reg_dest))
+#define ASM_NEG_REG(as, reg_dest) asm_arm_rsb_reg_reg_imm((as), (reg_dest), (reg_dest), 0)
 #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_arm_lsl_reg_reg((as), (reg_dest), (reg_shift))
 #define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_arm_lsr_reg_reg((as), (reg_dest), (reg_shift))
 #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_arm_asr_reg_reg((as), (reg_dest), (reg_shift))
diff --git a/py/asmthumb.h b/py/asmthumb.h
index 2829ac4c7e41..a9e68d7adbbe 100644
--- a/py/asmthumb.h
+++ b/py/asmthumb.h
@@ -406,6 +406,8 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel);
 #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_thumb_mov_reg_local_addr((as), (reg_dest), (local_num))
 #define ASM_MOV_REG_PCREL(as, rlo_dest, label) asm_thumb_mov_reg_pcrel((as), (rlo_dest), (label))
 
+#define ASM_NOT_REG(as, reg_dest) asm_thumb_mvn_rlo_rlo((as), (reg_dest), (reg_dest))
+#define ASM_NEG_REG(as, reg_dest) asm_thumb_neg_rlo_rlo((as), (reg_dest), (reg_dest))
 #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSL, (reg_dest), (reg_shift))
 #define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSR, (reg_dest), (reg_shift))
 #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_ASR, (reg_dest), (reg_shift))
diff --git a/py/asmx64.c b/py/asmx64.c
index abddc162696c..d9f33cfb2ad7 100644
--- a/py/asmx64.c
+++ b/py/asmx64.c
@@ -54,6 +54,8 @@
 #define OPCODE_MOVZX_RM8_TO_R64  (0xb6) /* 0x0f 0xb6/r */
 #define OPCODE_MOVZX_RM16_TO_R64 (0xb7) /* 0x0f 0xb7/r */
 #define OPCODE_LEA_MEM_TO_R64    (0x8d) /* /r */
+#define OPCODE_NOT_RM64          (0xf7) /* /2 */
+#define OPCODE_NEG_RM64          (0xf7) /* /3 */
 #define OPCODE_AND_R64_TO_RM64   (0x21) /* /r */
 #define OPCODE_OR_R64_TO_RM64    (0x09) /* /r */
 #define OPCODE_XOR_R64_TO_RM64   (0x31) /* /r */
@@ -362,6 +364,14 @@ void asm_x64_mov_i64_to_r64_optimised(asm_x64_t *as, int64_t src_i64, int dest_r
     }
 }
 
+void asm_x64_not_r64(asm_x64_t *as, int dest_r64) {
+    asm_x64_generic_r64_r64(as, dest_r64, 2, OPCODE_NOT_RM64);
+}
+
+void asm_x64_neg_r64(asm_x64_t *as, int dest_r64) {
+    asm_x64_generic_r64_r64(as, dest_r64, 3, OPCODE_NEG_RM64);
+}
+
 void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) {
     asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_AND_R64_TO_RM64);
 }
diff --git a/py/asmx64.h b/py/asmx64.h
index a8efc2bf6016..c63e31797ef5 100644
--- a/py/asmx64.h
+++ b/py/asmx64.h
@@ -97,6 +97,8 @@ void asm_x64_mov_mem8_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int des
 void asm_x64_mov_mem16_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64);
 void asm_x64_mov_mem32_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64);
 void asm_x64_mov_mem64_to_r64(asm_x64_t *as, int src_r64, int src_disp, int dest_r64);
+void asm_x64_not_r64(asm_x64_t *as, int dest_r64);
+void asm_x64_neg_r64(asm_x64_t *as, int dest_r64);
 void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64);
 void asm_x64_or_r64_r64(asm_x64_t *as, int dest_r64, int src_r64);
 void asm_x64_xor_r64_r64(asm_x64_t *as, int dest_r64, int src_r64);
@@ -191,6 +193,8 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32);
 #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_x64_mov_local_addr_to_r64((as), (local_num), (reg_dest))
 #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x64_mov_reg_pcrel((as), (reg_dest), (label))
 
+#define ASM_NOT_REG(as, reg) asm_x64_not_r64((as), (reg))
+#define ASM_NEG_REG(as, reg) asm_x64_neg_r64((as), (reg))
 #define ASM_LSL_REG(as, reg) asm_x64_shl_r64_cl((as), (reg))
 #define ASM_LSR_REG(as, reg) asm_x64_shr_r64_cl((as), (reg))
 #define ASM_ASR_REG(as, reg) asm_x64_sar_r64_cl((as), (reg))
diff --git a/py/asmx86.c b/py/asmx86.c
index 94e0213d65c0..4acac1b46ac4 100644
--- a/py/asmx86.c
+++ b/py/asmx86.c
@@ -54,6 +54,8 @@
 #define OPCODE_MOVZX_RM8_TO_R32  (0xb6) /* 0x0f 0xb6/r */
 #define OPCODE_MOVZX_RM16_TO_R32 (0xb7) /* 0x0f 0xb7/r */
 #define OPCODE_LEA_MEM_TO_R32    (0x8d) /* /r */
+#define OPCODE_NOT_RM32          (0xf7) /* /2 */
+#define OPCODE_NEG_RM32          (0xf7) /* /3 */
 #define OPCODE_AND_R32_TO_RM32   (0x21) /* /r */
 #define OPCODE_OR_R32_TO_RM32    (0x09) /* /r */
 #define OPCODE_XOR_R32_TO_RM32   (0x31) /* /r */
@@ -244,6 +246,14 @@ size_t asm_x86_mov_i32_to_r32(asm_x86_t *as, int32_t src_i32, int dest_r32) {
     return loc;
 }
 
+void asm_x86_not_r32(asm_x86_t *as, int dest_r32) {
+    asm_x86_generic_r32_r32(as, dest_r32, 2, OPCODE_NOT_RM32);
+}
+
+void asm_x86_neg_r32(asm_x86_t *as, int dest_r32) {
+    asm_x86_generic_r32_r32(as, dest_r32, 3, OPCODE_NEG_RM32);
+}
+
 void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) {
     asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_AND_R32_TO_RM32);
 }
diff --git a/py/asmx86.h b/py/asmx86.h
index ac98643a6a86..027d44151e82 100644
--- a/py/asmx86.h
+++ b/py/asmx86.h
@@ -92,6 +92,8 @@ void asm_x86_mov_r32_to_mem32(asm_x86_t *as, int src_r32, int dest_r32, int dest
 void asm_x86_mov_mem8_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32);
 void asm_x86_mov_mem16_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32);
 void asm_x86_mov_mem32_to_r32(asm_x86_t *as, int src_r32, int src_disp, int dest_r32);
+void asm_x86_not_r32(asm_x86_t *as, int dest_r32);
+void asm_x86_neg_r32(asm_x86_t *as, int dest_r32);
 void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32);
 void asm_x86_or_r32_r32(asm_x86_t *as, int dest_r32, int src_r32);
 void asm_x86_xor_r32_r32(asm_x86_t *as, int dest_r32, int src_r32);
@@ -186,6 +188,8 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r
 #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_x86_mov_local_addr_to_r32((as), (local_num), (reg_dest))
 #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x86_mov_reg_pcrel((as), (reg_dest), (label))
 
+#define ASM_NOT_REG(as, reg) asm_x86_not_r32((as), (reg))
+#define ASM_NEG_REG(as, reg) asm_x86_neg_r32((as), (reg))
 #define ASM_LSL_REG(as, reg) asm_x86_shl_r32_cl((as), (reg))
 #define ASM_LSR_REG(as, reg) asm_x86_shr_r32_cl((as), (reg))
 #define ASM_ASR_REG(as, reg) asm_x86_sar_r32_cl((as), (reg))
diff --git a/py/asmxtensa.c b/py/asmxtensa.c
index 84018402f69e..0fbe351dcf3c 100644
--- a/py/asmxtensa.c
+++ b/py/asmxtensa.c
@@ -185,7 +185,9 @@ size_t asm_xtensa_mov_reg_i32(asm_xtensa_t *as, uint reg_dest, uint32_t i32) {
 }
 
 void asm_xtensa_mov_reg_i32_optimised(asm_xtensa_t *as, uint reg_dest, uint32_t i32) {
-    if (SIGNED_FIT12(i32)) {
+    if (-32 <= (int)i32 && (int)i32 <= 95) {
+        asm_xtensa_op_movi_n(as, reg_dest, i32);
+    } else if (SIGNED_FIT12(i32)) {
         asm_xtensa_op_movi(as, reg_dest, i32);
     } else {
         asm_xtensa_mov_reg_i32(as, reg_dest, i32);
diff --git a/py/asmxtensa.h b/py/asmxtensa.h
index 5bd6426a1448..f226624a8263 100644
--- a/py/asmxtensa.h
+++ b/py/asmxtensa.h
@@ -203,14 +203,19 @@ static inline void asm_xtensa_op_movi(asm_xtensa_t *as, uint reg_dest, int32_t i
     asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRI8(2, 10, (imm12 >> 8) & 0xf, reg_dest, imm12 & 0xff));
 }
 
-static inline void asm_xtensa_op_movi_n(asm_xtensa_t *as, uint reg_dest, int imm4) {
-    asm_xtensa_op16(as, ASM_XTENSA_ENCODE_RI7(12, reg_dest, imm4));
+// Argument must be in the range (-32 .. 95) inclusive.
+static inline void asm_xtensa_op_movi_n(asm_xtensa_t *as, uint reg_dest, int imm7) {
+    asm_xtensa_op16(as, ASM_XTENSA_ENCODE_RI7(12, reg_dest, imm7));
 }
 
 static inline void asm_xtensa_op_mull(asm_xtensa_t *as, uint reg_dest, uint reg_src_a, uint reg_src_b) {
     asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 2, 8, reg_dest, reg_src_a, reg_src_b));
 }
 
+static inline void asm_xtensa_op_neg(asm_xtensa_t *as, uint reg_dest, uint reg_src) {
+    asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 0, 6, reg_dest, 0, reg_src));
+}
+
 static inline void asm_xtensa_op_or(asm_xtensa_t *as, uint reg_dest, uint reg_src_a, uint reg_src_b) {
     asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 0, 2, reg_dest, reg_src_a, reg_src_b));
 }
@@ -371,6 +376,7 @@ void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx);
 #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_xtensa_mov_reg_local_addr((as), (reg_dest), ASM_NUM_REGS_SAVED + (local_num))
 #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_xtensa_mov_reg_pcrel((as), (reg_dest), (label))
 
+#define ASM_NEG_REG(as, reg_dest) asm_xtensa_op_neg((as), (reg_dest), (reg_dest))
 #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) \
     do { \
         asm_xtensa_op_ssl((as), (reg_shift)); \
diff --git a/py/binary.c b/py/binary.c
index 4c8b6ffcdc49..7c01cfa1c828 100644
--- a/py/binary.c
+++ b/py/binary.c
@@ -74,11 +74,14 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
                 case 'S':
                     size = sizeof(void *);
                     break;
+                case 'e':
+                    size = 2;
+                    break;
                 case 'f':
-                    size = sizeof(float);
+                    size = 4;
                     break;
                 case 'd':
-                    size = sizeof(double);
+                    size = 8;
                     break;
             }
             break;
@@ -122,6 +125,10 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
                     align = alignof(void *);
                     size = sizeof(void *);
                     break;
+                case 'e':
+                    align = 2;
+                    size = 2;
+                    break;
                 case 'f':
                     align = alignof(float);
                     size = sizeof(float);
@@ -144,6 +151,99 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
     return size;
 }
 
+#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_USE_NATIVE_FLT16
+
+static inline float mp_decode_half_float(uint16_t hf) {
+    union {
+        uint16_t i;
+        _Float16 f;
+    } fpu = { .i = hf };
+    return fpu.f;
+}
+
+static inline uint16_t mp_encode_half_float(float x) {
+    union {
+        uint16_t i;
+        _Float16 f;
+    } fp_sp = { .f = (_Float16)x };
+    return fp_sp.i;
+}
+
+#elif MICROPY_PY_BUILTINS_FLOAT
+
+static float mp_decode_half_float(uint16_t hf) {
+    union {
+        uint32_t i;
+        float f;
+    } fpu;
+
+    uint16_t m = hf & 0x3ff;
+    int e = (hf >> 10) & 0x1f;
+    if (e == 0x1f) {
+        // Half-float is infinity.
+        e = 0xff;
+    } else if (e) {
+        // Half-float is normal.
+        e += 127 - 15;
+    } else if (m) {
+        // Half-float is subnormal, make it normal.
+        e = 127 - 15;
+        while (!(m & 0x400)) {
+            m <<= 1;
+            --e;
+        }
+        m -= 0x400;
+        ++e;
+    }
+
+    fpu.i = ((hf & 0x8000) << 16) | (e << 23) | (m << 13);
+    return fpu.f;
+}
+
+static uint16_t mp_encode_half_float(float x) {
+    union {
+        uint32_t i;
+        float f;
+    } fpu = { .f = x };
+
+    uint16_t m = (fpu.i >> 13) & 0x3ff;
+    if (fpu.i & (1 << 12)) {
+        // Round up.
+        ++m;
+    }
+    int e = (fpu.i >> 23) & 0xff;
+
+    if (e == 0xff) {
+        // Infinity.
+        e = 0x1f;
+    } else if (e != 0) {
+        e -= 127 - 15;
+        if (e < 0) {
+            // Underflow: denormalized, or zero.
+            if (e >= -11) {
+                m = (m | 0x400) >> -e;
+                if (m & 1) {
+                    m = (m >> 1) + 1;
+                } else {
+                    m >>= 1;
+                }
+            } else {
+                m = 0;
+            }
+            e = 0;
+        } else if (e > 0x3f) {
+            // Overflow: infinity.
+            e = 0x1f;
+            m = 0;
+        }
+    }
+
+    uint16_t bits = ((fpu.i >> 16) & 0x8000) | (e << 10) | m;
+    return bits;
+}
+
+#endif
+
 mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) {
     mp_int_t val = 0;
     switch (typecode) {
@@ -240,6 +340,8 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte *
         const char *s_val = (const char *)(uintptr_t)(mp_uint_t)val;
         return mp_obj_new_str(s_val, strlen(s_val));
     #if MICROPY_PY_BUILTINS_FLOAT
+    } else if (val_type == 'e') {
+        return mp_obj_new_float_from_f(mp_decode_half_float(val));
     } else if (val_type == 'f') {
         union {
             uint32_t i;
@@ -309,6 +411,9 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p
             val = (mp_uint_t)val_in;
             break;
         #if MICROPY_PY_BUILTINS_FLOAT
+        case 'e':
+            val = mp_encode_half_float(mp_obj_get_float_to_f(val_in));
+            break;
         case 'f': {
             union {
                 uint32_t i;
diff --git a/py/compile.c b/py/compile.c
index 7a359e662e73..62757de3c083 100644
--- a/py/compile.c
+++ b/py/compile.c
@@ -196,6 +196,10 @@ typedef struct _compiler_t {
     mp_emit_common_t emit_common;
 } compiler_t;
 
+#if MICROPY_COMP_ALLOW_TOP_LEVEL_AWAIT
+bool mp_compile_allow_top_level_await = false;
+#endif
+
 /******************************************************************************/
 // mp_emit_common_t helper functions
 // These are defined here so they can be inlined, to reduce code size.
@@ -2759,8 +2763,13 @@ static void compile_yield_expr(compiler_t *comp, mp_parse_node_struct_t *pns) {
 #if MICROPY_PY_ASYNC_AWAIT
 static void compile_atom_expr_await(compiler_t *comp, mp_parse_node_struct_t *pns) {
     if (comp->scope_cur->kind != SCOPE_FUNCTION && comp->scope_cur->kind != SCOPE_LAMBDA) {
-        compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'await' outside function"));
-        return;
+        #if MICROPY_COMP_ALLOW_TOP_LEVEL_AWAIT
+        if (!mp_compile_allow_top_level_await)
+        #endif
+        {
+            compile_syntax_error(comp, (mp_parse_node_t)pns, MP_ERROR_TEXT("'await' outside function"));
+            return;
+        }
     }
     compile_atom_expr_normal(comp, pns);
     compile_yield_from(comp);
diff --git a/py/compile.h b/py/compile.h
index 5e0fd8b31c4a..f9970a521d64 100644
--- a/py/compile.h
+++ b/py/compile.h
@@ -30,6 +30,11 @@
 #include "py/parse.h"
 #include "py/emitglue.h"
 
+#if MICROPY_COMP_ALLOW_TOP_LEVEL_AWAIT
+// set to `true` to allow top-level await expressions
+extern bool mp_compile_allow_top_level_await;
+#endif
+
 // the compiler will raise an exception if an error occurred
 // the compiler will clear the parse tree before it returns
 // mp_globals_get() will be used for the context
diff --git a/py/dynruntime.h b/py/dynruntime.h
index 7765fca40d59..d07b1dce2c4e 100644
--- a/py/dynruntime.h
+++ b/py/dynruntime.h
@@ -29,6 +29,7 @@
 // This header file contains definitions to dynamically implement the static
 // MicroPython runtime API defined in py/obj.h and py/runtime.h.
 
+#include "py/binary.h"
 #include "py/nativeglue.h"
 #include "py/objfun.h"
 #include "py/objstr.h"
@@ -184,6 +185,10 @@ static inline void *mp_obj_malloc_helper_dyn(size_t num_bytes, const mp_obj_type
 /******************************************************************************/
 // General runtime functions
 
+#define mp_binary_get_size(struct_type, val_type, palign) (mp_fun_table.binary_get_size((struct_type), (val_type), (palign)))
+#define mp_binary_get_val_array(typecode, p, index) (mp_fun_table.binary_get_val_array((typecode), (p), (index)))
+#define mp_binary_set_val_array(typecode, p, index, val_in) (mp_fun_table.binary_set_val_array((typecode), (p), (index), (val_in)))
+
 #define mp_load_name(qst)                 (mp_fun_table.load_name((qst)))
 #define mp_load_global(qst)               (mp_fun_table.load_global((qst)))
 #define mp_load_attr(base, attr)          (mp_fun_table.load_attr((base), (attr)))
diff --git a/py/emitnative.c b/py/emitnative.c
index f80461dd42f3..0b84a2ec8a85 100644
--- a/py/emitnative.c
+++ b/py/emitnative.c
@@ -2259,15 +2259,38 @@ static void emit_native_pop_except_jump(emit_t *emit, mp_uint_t label, bool with
 }
 
 static void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) {
-    vtype_kind_t vtype;
-    emit_pre_pop_reg(emit, &vtype, REG_ARG_2);
-    if (vtype == VTYPE_PYOBJ) {
+    vtype_kind_t vtype = peek_vtype(emit, 0);
+    if (vtype == VTYPE_INT || vtype == VTYPE_UINT) {
+        if (op == MP_UNARY_OP_POSITIVE) {
+            // No-operation, just leave the argument on the stack.
+        } else if (op == MP_UNARY_OP_NEGATIVE) {
+            int reg = REG_RET;
+            emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg);
+            ASM_NEG_REG(emit->as, reg);
+            emit_post_push_reg(emit, vtype, reg);
+        } else if (op == MP_UNARY_OP_INVERT) {
+            #ifdef ASM_NOT_REG
+            int reg = REG_RET;
+            emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg);
+            ASM_NOT_REG(emit->as, reg);
+            #else
+            int reg = REG_RET;
+            emit_pre_pop_reg_flexible(emit, &vtype, ®, REG_ARG_1, reg);
+            ASM_MOV_REG_IMM(emit->as, REG_ARG_1, -1);
+            ASM_XOR_REG_REG(emit->as, reg, REG_ARG_1);
+            #endif
+            emit_post_push_reg(emit, vtype, reg);
+        } else {
+            EMIT_NATIVE_VIPER_TYPE_ERROR(emit,
+                MP_ERROR_TEXT("'not' not implemented"), mp_binary_op_method_name[op]);
+        }
+    } else if (vtype == VTYPE_PYOBJ) {
+        emit_pre_pop_reg(emit, &vtype, REG_ARG_2);
         emit_call_with_imm_arg(emit, MP_F_UNARY_OP, op, REG_ARG_1);
         emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET);
     } else {
-        adjust_stack(emit, 1);
         EMIT_NATIVE_VIPER_TYPE_ERROR(emit,
-            MP_ERROR_TEXT("unary op %q not implemented"), mp_unary_op_method_name[op]);
+            MP_ERROR_TEXT("can't do unary op of '%q'"), vtype_to_qstr(vtype));
     }
 }
 
diff --git a/py/makeqstrdata.py b/py/makeqstrdata.py
index 3e5e7930a5b8..480344180a88 100644
--- a/py/makeqstrdata.py
+++ b/py/makeqstrdata.py
@@ -223,11 +223,11 @@
     "zip",
 ]
 
-# Additional QSTRs that must have index <255 because they are stored in
-# `mp_binary_op_method_name` and `mp_unary_op_method_name` (see py/objtype.c).
+# Additional QSTRs that must have index <255 because they are stored as `byte` values.
 # These are not part of the .mpy compatibility list, but we place them in the
 # fixed unsorted pool (i.e. QDEF0) to ensure their indices are small.
-operator_qstr_list = {
+unsorted_qstr_list = {
+    # From py/objtype.c: used in the `mp_binary_op_method_name` and `mp_unary_op_method_name` tables.
     "__bool__",
     "__pos__",
     "__neg__",
@@ -286,6 +286,13 @@
     "__get__",
     "__set__",
     "__delete__",
+    # From py/scope.c: used in `scope_simple_name_table` table.
+    # Note: "" is already in `static_qstr_list`.
+    "",
+    "",
+    "",
+    "",
+    "",
 }
 
 
@@ -404,10 +411,10 @@ def print_qstr_data(qcfgs, qstrs):
         print("QDEF0(MP_QSTR_%s, %s)" % (qstr_escape(qstr), qbytes))
 
     # add remaining qstrs to the sorted (by value) pool (unless they're in
-    # operator_qstr_list, in which case add them to the unsorted pool)
+    # unsorted_qstr_list, in which case add them to the unsorted pool)
     for ident, qstr in sorted(qstrs.values(), key=lambda x: x[1]):
         qbytes = make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr)
-        pool = 0 if qstr in operator_qstr_list else 1
+        pool = 0 if qstr in unsorted_qstr_list else 1
         print("QDEF%d(MP_QSTR_%s, %s)" % (pool, ident, qbytes))
 
 
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 90f8e592bfe8..af2480266bca 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -446,6 +446,11 @@
 #define MICROPY_DYNAMIC_COMPILER (0)
 #endif
 
+// Whether the compiler allows compiling top-level await expressions
+#ifndef MICROPY_COMP_ALLOW_TOP_LEVEL_AWAIT
+#define MICROPY_COMP_ALLOW_TOP_LEVEL_AWAIT (0)
+#endif
+
 // Whether to enable constant folding; eg 1+2 rewritten as 3
 #ifndef MICROPY_COMP_CONST_FOLDING
 #define MICROPY_COMP_CONST_FOLDING (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
@@ -830,6 +835,15 @@ typedef double mp_float_t;
 #define MICROPY_PY_BUILTINS_COMPLEX (MICROPY_PY_BUILTINS_FLOAT)
 #endif
 
+// Whether to use the native _Float16 for 16-bit float support
+#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16
+#ifdef __FLT16_MAX__
+#define MICROPY_FLOAT_USE_NATIVE_FLT16 (1)
+#else
+#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0)
+#endif
+#endif
+
 // Whether to provide a high-quality hash for float and complex numbers.
 // Otherwise the default is a very simple but correct hashing function.
 #ifndef MICROPY_FLOAT_HIGH_QUALITY_HASH
@@ -1303,6 +1317,16 @@ typedef double mp_float_t;
 #define MICROPY_PY_COLLECTIONS_DEQUE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
 #endif
 
+// Whether "collections.deque" supports iteration
+#ifndef MICROPY_PY_COLLECTIONS_DEQUE_ITER
+#define MICROPY_PY_COLLECTIONS_DEQUE_ITER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
+
+// Whether "collections.deque" supports subscription
+#ifndef MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR
+#define MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
+#endif
+
 // Whether to provide "collections.OrderedDict" type
 #ifndef MICROPY_PY_COLLECTIONS_ORDEREDDICT
 #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
@@ -1692,6 +1716,11 @@ typedef double mp_float_t;
 #define MICROPY_PY_MACHINE (0)
 #endif
 
+// Whether to include: reset, reset_cause
+#ifndef MICROPY_PY_MACHINE_RESET
+#define MICROPY_PY_MACHINE_RESET (0)
+#endif
+
 // Whether to include: bitstream
 #ifndef MICROPY_PY_MACHINE_BITSTREAM
 #define MICROPY_PY_MACHINE_BITSTREAM (0)
@@ -1702,6 +1731,16 @@ typedef double mp_float_t;
 #define MICROPY_PY_MACHINE_PULSE (0)
 #endif
 
+// Whether to provide the "machine.mem8/16/32" objects
+#ifndef MICROPY_PY_MACHINE_MEMX
+#define MICROPY_PY_MACHINE_MEMX (MICROPY_PY_MACHINE)
+#endif
+
+// Whether to provide the "machine.Signal" class
+#ifndef MICROPY_PY_MACHINE_SIGNAL
+#define MICROPY_PY_MACHINE_SIGNAL (MICROPY_PY_MACHINE)
+#endif
+
 #ifndef MICROPY_PY_MACHINE_I2C
 #define MICROPY_PY_MACHINE_I2C (0)
 #endif
diff --git a/py/nativeglue.c b/py/nativeglue.c
index 4cd090c0a2a7..ba3d93f76070 100644
--- a/py/nativeglue.c
+++ b/py/nativeglue.c
@@ -29,6 +29,7 @@
 #include 
 #include 
 
+#include "py/binary.h"
 #include "py/runtime.h"
 #include "py/smallint.h"
 #include "py/nativeglue.h"
@@ -330,6 +331,9 @@ const mp_fun_table_t mp_fun_table = {
     mp_obj_get_float_to_d,
     mp_get_buffer,
     mp_get_stream_raise,
+    mp_binary_get_size,
+    mp_binary_get_val_array,
+    mp_binary_set_val_array,
     &mp_plat_print,
     &mp_type_type,
     &mp_type_str,
diff --git a/py/nativeglue.h b/py/nativeglue.h
index 113f5fde6528..1fa85933452c 100644
--- a/py/nativeglue.h
+++ b/py/nativeglue.h
@@ -156,7 +156,12 @@ typedef struct _mp_fun_table_t {
     double (*obj_get_float_to_d)(mp_obj_t o);
     bool (*get_buffer)(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags);
     const mp_stream_p_t *(*get_stream_raise)(mp_obj_t self_in, int flags);
+    size_t (*binary_get_size)(char struct_type, char val_type, size_t *palign);
+    mp_obj_t (*binary_get_val_array)(char typecode, void *p, size_t index);
+    void (*binary_set_val_array)(char typecode, void *p, size_t index, mp_obj_t val_in);
     const mp_print_t *plat_print;
+    // The following entries start at index 70 and are referenced by tools-mpy_ld.py,
+    // see constant MP_FUN_TABLE_MP_TYPE_TYPE_OFFSET.
     const mp_obj_type_t *type_type;
     const mp_obj_type_t *type_str;
     const mp_obj_type_t *type_list;
diff --git a/py/nlraarch64.c b/py/nlraarch64.c
index fcc318f2dc18..d6d87ebc50db 100644
--- a/py/nlraarch64.c
+++ b/py/nlraarch64.c
@@ -75,7 +75,7 @@ NORETURN void nlr_jump(void *val) {
         "ret                     \n"
         :
         : "r" (top)
-        :
+        : "memory"
         );
 
     MP_UNREACHABLE
diff --git a/py/nlrmips.c b/py/nlrmips.c
index bd5d73b6f767..cba52b16a266 100644
--- a/py/nlrmips.c
+++ b/py/nlrmips.c
@@ -78,7 +78,7 @@ NORETURN void nlr_jump(void *val) {
         "nop            \n"
         :
         : "r" (top)
-        :
+        : "memory"
         );
     MP_UNREACHABLE
 }
diff --git a/py/nlrpowerpc.c b/py/nlrpowerpc.c
index 448459216b6e..8a69fe1eeca6 100644
--- a/py/nlrpowerpc.c
+++ b/py/nlrpowerpc.c
@@ -114,7 +114,7 @@ NORETURN void nlr_jump(void *val) {
         "blr ;"
         :
         : "r" (&top->regs)
-        :
+        : "memory"
         );
 
     MP_UNREACHABLE;
@@ -203,7 +203,7 @@ NORETURN void nlr_jump(void *val) {
         "blr ;"
         :
         : "r" (&top->regs)
-        :
+        : "memory"
         );
 
     MP_UNREACHABLE;
diff --git a/py/nlrthumb.c b/py/nlrthumb.c
index a8ffecc47033..a22c5df5b941 100644
--- a/py/nlrthumb.c
+++ b/py/nlrthumb.c
@@ -132,7 +132,7 @@ NORETURN void nlr_jump(void *val) {
         "bx     lr                  \n" // return
         :                           // output operands
         : "r" (top)                 // input operands
-        :                           // clobbered registers
+        : "memory"                  // clobbered registers
         );
 
     MP_UNREACHABLE
diff --git a/py/nlrx64.c b/py/nlrx64.c
index 6b7d0262f549..d1ad91ff7d71 100644
--- a/py/nlrx64.c
+++ b/py/nlrx64.c
@@ -123,7 +123,7 @@ NORETURN void nlr_jump(void *val) {
         "ret                        \n" // return
         :                           // output operands
         : "r" (top)                 // input operands
-        :                           // clobbered registers
+        : "memory"                  // clobbered registers
         );
 
     MP_UNREACHABLE
diff --git a/py/nlrx86.c b/py/nlrx86.c
index f658d41910c8..085e30d2034a 100644
--- a/py/nlrx86.c
+++ b/py/nlrx86.c
@@ -95,7 +95,7 @@ NORETURN void nlr_jump(void *val) {
         "ret                        \n" // return
         :                           // output operands
         : "r" (top)                 // input operands
-        :                           // clobbered registers
+        : "memory"                  // clobbered registers
         );
 
     MP_UNREACHABLE
diff --git a/py/nlrxtensa.c b/py/nlrxtensa.c
index abe9042af9f1..ff7af6edeef9 100644
--- a/py/nlrxtensa.c
+++ b/py/nlrxtensa.c
@@ -74,7 +74,7 @@ NORETURN void nlr_jump(void *val) {
         "ret.n                      \n" // return
         :                           // output operands
         : "r" (top)                 // input operands
-        :                           // clobbered registers
+        : "memory"                  // clobbered registers
         );
 
     MP_UNREACHABLE
diff --git a/py/objdeque.c b/py/objdeque.c
index 68e162179311..583537017fdb 100644
--- a/py/objdeque.c
+++ b/py/objdeque.c
@@ -25,13 +25,11 @@
  */
 
 #include  // for ssize_t
-#include 
-
-#include "py/mpconfig.h"
-#if MICROPY_PY_COLLECTIONS_DEQUE
 
 #include "py/runtime.h"
 
+#if MICROPY_PY_COLLECTIONS_DEQUE
+
 typedef struct _mp_obj_deque_t {
     mp_obj_base_t base;
     size_t alloc;
@@ -42,15 +40,15 @@ typedef struct _mp_obj_deque_t {
     #define FLAG_CHECK_OVERFLOW 1
 } mp_obj_deque_t;
 
+static mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg);
+static mp_obj_t mp_obj_deque_extend(mp_obj_t self_in, mp_obj_t arg_in);
+#if MICROPY_PY_COLLECTIONS_DEQUE_ITER
+static mp_obj_t mp_obj_new_deque_it(mp_obj_t deque, mp_obj_iter_buf_t *iter_buf);
+#endif
+
 static mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
     mp_arg_check_num(n_args, n_kw, 2, 3, false);
 
-    /* Initialization from existing sequence is not supported, so an empty
-       tuple must be passed as such. */
-    if (args[0] != mp_const_empty_tuple) {
-        mp_raise_ValueError(NULL);
-    }
-
     // Protect against -1 leading to zero-length allocation and bad array access
     mp_int_t maxlen = mp_obj_get_int(args[1]);
     if (maxlen < 0) {
@@ -66,21 +64,27 @@ static mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t
         o->flags = mp_obj_get_int(args[2]);
     }
 
+    mp_obj_deque_extend(MP_OBJ_FROM_PTR(o), args[0]);
+
     return MP_OBJ_FROM_PTR(o);
 }
 
+static size_t deque_len(mp_obj_deque_t *self) {
+    ssize_t len = self->i_put - self->i_get;
+    if (len < 0) {
+        len += self->alloc;
+    }
+    return len;
+}
+
 static mp_obj_t deque_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
     mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
     switch (op) {
         case MP_UNARY_OP_BOOL:
             return mp_obj_new_bool(self->i_get != self->i_put);
-        case MP_UNARY_OP_LEN: {
-            ssize_t len = self->i_put - self->i_get;
-            if (len < 0) {
-                len += self->alloc;
-            }
-            return MP_OBJ_NEW_SMALL_INT(len);
-        }
+        case MP_UNARY_OP_LEN:
+            return MP_OBJ_NEW_SMALL_INT(deque_len(self));
+
         #if MICROPY_PY_SYS_GETSIZEOF
         case MP_UNARY_OP_SIZEOF: {
             size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc;
@@ -117,6 +121,45 @@ static mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg) {
 }
 static MP_DEFINE_CONST_FUN_OBJ_2(deque_append_obj, mp_obj_deque_append);
 
+static mp_obj_t mp_obj_deque_appendleft(mp_obj_t self_in, mp_obj_t arg) {
+    mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
+
+    size_t new_i_get = self->i_get - 1;
+    if (self->i_get == 0) {
+        new_i_get = self->alloc - 1;
+    }
+
+    if (self->flags & FLAG_CHECK_OVERFLOW && new_i_get == self->i_put) {
+        mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("full"));
+    }
+
+    self->i_get = new_i_get;
+    self->items[self->i_get] = arg;
+
+    // overwriting first element in deque
+    if (self->i_put == new_i_get) {
+        if (self->i_put == 0) {
+            self->i_put = self->alloc - 1;
+        } else {
+            self->i_put--;
+        }
+    }
+
+    return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(deque_appendleft_obj, mp_obj_deque_appendleft);
+
+static mp_obj_t mp_obj_deque_extend(mp_obj_t self_in, mp_obj_t arg_in) {
+    mp_obj_iter_buf_t iter_buf;
+    mp_obj_t iter = mp_getiter(arg_in, &iter_buf);
+    mp_obj_t item;
+    while ((item = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) {
+        mp_obj_deque_append(self_in, item);
+    }
+    return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(deque_extend_obj, mp_obj_deque_extend);
+
 static mp_obj_t deque_popleft(mp_obj_t self_in) {
     mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
 
@@ -135,6 +178,51 @@ static mp_obj_t deque_popleft(mp_obj_t self_in) {
 }
 static MP_DEFINE_CONST_FUN_OBJ_1(deque_popleft_obj, deque_popleft);
 
+static mp_obj_t deque_pop(mp_obj_t self_in) {
+    mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
+
+    if (self->i_get == self->i_put) {
+        mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty"));
+    }
+
+    if (self->i_put == 0) {
+        self->i_put = self->alloc - 1;
+    } else {
+        self->i_put--;
+    }
+
+    mp_obj_t ret = self->items[self->i_put];
+    self->items[self->i_put] = MP_OBJ_NULL;
+
+    return ret;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(deque_pop_obj, deque_pop);
+
+#if MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR
+static mp_obj_t deque_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
+    if (value == MP_OBJ_NULL) {
+        // delete not supported, fall back to mp_obj_subscr() error message
+        return MP_OBJ_NULL;
+    }
+    mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
+
+    size_t offset = mp_get_index(self->base.type, deque_len(self), index, false);
+    size_t index_val = self->i_get + offset;
+    if (index_val > self->alloc) {
+        index_val -= self->alloc;
+    }
+
+    if (value == MP_OBJ_SENTINEL) {
+        // load
+        return self->items[index_val];
+    } else {
+        // store into deque
+        self->items[index_val] = value;
+        return mp_const_none;
+    }
+}
+#endif
+
 #if 0
 static mp_obj_t deque_clear(mp_obj_t self_in) {
     mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
@@ -147,21 +235,80 @@ static MP_DEFINE_CONST_FUN_OBJ_1(deque_clear_obj, deque_clear);
 
 static const mp_rom_map_elem_t deque_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&deque_append_obj) },
+    { MP_ROM_QSTR(MP_QSTR_appendleft), MP_ROM_PTR(&deque_appendleft_obj) },
+    { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&deque_extend_obj) },
     #if 0
     { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&deque_clear_obj) },
     #endif
+    { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&deque_pop_obj) },
     { MP_ROM_QSTR(MP_QSTR_popleft), MP_ROM_PTR(&deque_popleft_obj) },
 };
 
 static MP_DEFINE_CONST_DICT(deque_locals_dict, deque_locals_dict_table);
 
+#if MICROPY_PY_COLLECTIONS_DEQUE_ITER
+#define DEQUE_TYPE_FLAGS MP_TYPE_FLAG_ITER_IS_GETITER
+#define DEQUE_TYPE_ITER iter, mp_obj_new_deque_it,
+#else
+#define DEQUE_TYPE_FLAGS MP_TYPE_FLAG_NONE
+#define DEQUE_TYPE_ITER
+#endif
+
+#if MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR
+#define DEQUE_TYPE_SUBSCR subscr, deque_subscr,
+#else
+#define DEQUE_TYPE_SUBSCR
+#endif
+
 MP_DEFINE_CONST_OBJ_TYPE(
     mp_type_deque,
     MP_QSTR_deque,
-    MP_TYPE_FLAG_NONE,
+    MP_TYPE_FLAG_ITER_IS_GETITER,
     make_new, deque_make_new,
     unary_op, deque_unary_op,
+    DEQUE_TYPE_SUBSCR
+    DEQUE_TYPE_ITER
     locals_dict, &deque_locals_dict
     );
 
+/******************************************************************************/
+/* deque iterator                                                             */
+
+#if MICROPY_PY_COLLECTIONS_DEQUE_ITER
+
+typedef struct _mp_obj_deque_it_t {
+    mp_obj_base_t base;
+    mp_fun_1_t iternext;
+    mp_obj_t deque;
+    size_t cur;
+} mp_obj_deque_it_t;
+
+static mp_obj_t deque_it_iternext(mp_obj_t self_in) {
+    mp_obj_deque_it_t *self = MP_OBJ_TO_PTR(self_in);
+    mp_obj_deque_t *deque = MP_OBJ_TO_PTR(self->deque);
+    if (self->cur != deque->i_put) {
+        mp_obj_t o_out = deque->items[self->cur];
+        if (++self->cur == deque->alloc) {
+            self->cur = 0;
+        }
+        return o_out;
+    } else {
+        return MP_OBJ_STOP_ITERATION;
+    }
+}
+
+static mp_obj_t mp_obj_new_deque_it(mp_obj_t deque, mp_obj_iter_buf_t *iter_buf) {
+    mp_obj_deque_t *deque_ = MP_OBJ_TO_PTR(deque);
+    size_t i_get = deque_->i_get;
+    assert(sizeof(mp_obj_deque_it_t) <= sizeof(mp_obj_iter_buf_t));
+    mp_obj_deque_it_t *o = (mp_obj_deque_it_t *)iter_buf;
+    o->base.type = &mp_type_polymorph_iter;
+    o->iternext = deque_it_iternext;
+    o->deque = deque;
+    o->cur = i_get;
+    return MP_OBJ_FROM_PTR(o);
+}
+
+#endif
+
 #endif // MICROPY_PY_COLLECTIONS_DEQUE
diff --git a/py/objstr.h b/py/objstr.h
index 72fe1cfef01a..028fc9597ffc 100644
--- a/py/objstr.h
+++ b/py/objstr.h
@@ -100,6 +100,8 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s
     mp_obj_t index, bool is_slice);
 const byte *find_subbytes(const byte *haystack, size_t hlen, const byte *needle, size_t nlen, int direction);
 
+#define MP_DEFINE_BYTES_OBJ(obj_name, target, len) mp_obj_str_t obj_name = {{&mp_type_bytes}, 0, (len), (const byte *)(target)}
+
 mp_obj_t mp_obj_bytes_hex(size_t n_args, const mp_obj_t *args, const mp_obj_type_t *type);
 mp_obj_t mp_obj_bytes_fromhex(mp_obj_t type_in, mp_obj_t data);
 
diff --git a/py/objtype.c b/py/objtype.c
index 8b2eb6de04ee..b6d600e9434d 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -370,6 +370,8 @@ static mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_arg
 
 // Qstrs for special methods are guaranteed to have a small value, so we use byte
 // type to represent them.
+// The (unescaped) names appear in `unsorted_str_list` in the QSTR
+// generator script py/makeqstrdata.py to ensure they are assigned low numbers.
 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__,
@@ -468,6 +470,8 @@ static mp_obj_t instance_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
 // 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.
+// The (unescaped) names appear in `unsorted_str_list` in the QSTR
+// generator script py/makeqstrdata.py to ensure they are assigned low numbers.
 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__,
diff --git a/py/parse.c b/py/parse.c
index 54be8b97d092..1392303e6ff9 100644
--- a/py/parse.c
+++ b/py/parse.c
@@ -1386,6 +1386,7 @@ void mp_parse_tree_clear(mp_parse_tree_t *tree) {
         m_del(byte, chunk, sizeof(mp_parse_chunk_t) + chunk->alloc);
         chunk = next;
     }
+    tree->chunk = NULL; // Avoid dangling pointer that may live on stack
 }
 
 #endif // MICROPY_ENABLE_COMPILER
diff --git a/py/persistentcode.h b/py/persistentcode.h
index 45a268d63fb9..d2b310f241f8 100644
--- a/py/persistentcode.h
+++ b/py/persistentcode.h
@@ -35,7 +35,7 @@
 // set) must also match MPY_SUB_VERSION. This allows 3 additional updates to
 // the native ABI per bytecode revision.
 #define MPY_VERSION 6
-#define MPY_SUB_VERSION 2
+#define MPY_SUB_VERSION 3
 
 // Macros to encode/decode sub-version to/from the feature byte. This replaces
 // the bits previously used to encode the flags (map caching and unicode)
diff --git a/py/qstr.c b/py/qstr.c
index 3720932250ca..fea7f44fe13b 100644
--- a/py/qstr.c
+++ b/py/qstr.c
@@ -79,7 +79,7 @@ size_t qstr_compute_hash(const byte *data, size_t len) {
 // it is part of the .mpy ABI. See the top of py/persistentcode.c and
 // static_qstr_list in makeqstrdata.py. This pool is unsorted (although in a
 // future .mpy version we could re-order them and make it sorted). It also
-// contains additional qstrs that must have IDs <256, see operator_qstr_list
+// contains additional qstrs that must have IDs <256, see unsorted_qstr_list
 // in makeqstrdata.py.
 #if MICROPY_QSTR_BYTES_IN_HASH
 const qstr_hash_t mp_qstr_const_hashes_static[] = {
diff --git a/py/runtime.c b/py/runtime.c
index f7e0abdb4600..1836f5d92a85 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -171,6 +171,10 @@ void mp_init(void) {
     MP_STATE_VM(bluetooth) = MP_OBJ_NULL;
     #endif
 
+    #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+    MP_STATE_VM(usbd) = MP_OBJ_NULL;
+    #endif
+
     #if MICROPY_PY_THREAD_GIL
     mp_thread_mutex_init(&MP_STATE_VM(gil_mutex));
     #endif
diff --git a/py/scope.c b/py/scope.c
index 1a4c6cc77f15..4893e7cc4e5e 100644
--- a/py/scope.c
+++ b/py/scope.c
@@ -31,6 +31,8 @@
 #if MICROPY_ENABLE_COMPILER
 
 // These low numbered qstrs should fit in 8 bits.  See assertions below.
+// The (unescaped) names appear in `unsorted_str_list` in the QSTR
+// generator script py/makeqstrdata.py to ensure they are assigned low numbers.
 static const uint8_t scope_simple_name_table[] = {
     [SCOPE_MODULE] = MP_QSTR__lt_module_gt_,
     [SCOPE_LAMBDA] = MP_QSTR__lt_lambda_gt_,
diff --git a/py/stream.c b/py/stream.c
index aa905ca8cc90..d7a8881e1a32 100644
--- a/py/stream.c
+++ b/py/stream.c
@@ -82,6 +82,18 @@ mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf_, mp_uint_t size, int *errcode
     return done;
 }
 
+mp_off_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode) {
+    struct mp_stream_seek_t seek_s;
+    seek_s.offset = offset;
+    seek_s.whence = whence;
+    const mp_stream_p_t *stream_p = mp_get_stream(stream);
+    mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, errcode);
+    if (res == MP_STREAM_ERROR) {
+        return (mp_off_t)-1;
+    }
+    return seek_s.offset;
+}
+
 const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) {
     const mp_obj_type_t *type = mp_obj_get_type(self_in);
     if (MP_OBJ_TYPE_HAS_SLOT(type, protocol)) {
@@ -445,28 +457,26 @@ static mp_obj_t mp_stream___exit__(size_t n_args, const mp_obj_t *args) {
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream___exit___obj, 4, 4, mp_stream___exit__);
 
 static mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) {
-    struct mp_stream_seek_t seek_s;
     // TODO: Could be uint64
-    seek_s.offset = mp_obj_get_int(args[1]);
-    seek_s.whence = SEEK_SET;
+    mp_off_t offset = mp_obj_get_int(args[1]);
+    int whence = SEEK_SET;
     if (n_args == 3) {
-        seek_s.whence = mp_obj_get_int(args[2]);
+        whence = mp_obj_get_int(args[2]);
     }
 
     // In POSIX, it's error to seek before end of stream, we enforce it here.
-    if (seek_s.whence == SEEK_SET && seek_s.offset < 0) {
+    if (whence == SEEK_SET && offset < 0) {
         mp_raise_OSError(MP_EINVAL);
     }
 
-    const mp_stream_p_t *stream_p = mp_get_stream(args[0]);
     int error;
-    mp_uint_t res = stream_p->ioctl(args[0], MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
-    if (res == MP_STREAM_ERROR) {
+    mp_off_t res = mp_stream_seek(args[0], offset, whence, &error);
+    if (res == (mp_off_t)-1) {
         mp_raise_OSError(error);
     }
 
     // TODO: Could be uint64
-    return mp_obj_new_int_from_uint(seek_s.offset);
+    return mp_obj_new_int_from_uint(res);
 }
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_seek_obj, 2, 3, stream_seek);
 
@@ -545,16 +555,11 @@ ssize_t mp_stream_posix_read(void *stream, void *buf, size_t len) {
 }
 
 off_t mp_stream_posix_lseek(void *stream, off_t offset, int whence) {
-    const mp_obj_base_t *o = stream;
-    const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol);
-    struct mp_stream_seek_t seek_s;
-    seek_s.offset = offset;
-    seek_s.whence = whence;
-    mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &errno);
-    if (res == MP_STREAM_ERROR) {
+    mp_off_t res = mp_stream_seek(MP_OBJ_FROM_PTR(stream), offset, whence, &errno);
+    if (res == (mp_off_t)-1) {
         return -1;
     }
-    return seek_s.offset;
+    return res;
 }
 
 int mp_stream_posix_fsync(void *stream) {
diff --git a/py/stream.h b/py/stream.h
index e6e6f283df17..7c4d38afa9b6 100644
--- a/py/stream.h
+++ b/py/stream.h
@@ -115,6 +115,7 @@ mp_obj_t mp_stream_write(mp_obj_t self_in, const void *buf, size_t len, byte fla
 mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf, mp_uint_t size, int *errcode, byte flags);
 #define mp_stream_write_exactly(stream, buf, size, err) mp_stream_rw(stream, (byte *)buf, size, err, MP_STREAM_RW_WRITE)
 #define mp_stream_read_exactly(stream, buf, size, err) mp_stream_rw(stream, buf, size, err, MP_STREAM_RW_READ)
+mp_off_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode);
 
 void mp_stream_write_adaptor(void *self, const char *buf, size_t len);
 
diff --git a/pyproject.toml b/pyproject.toml
index 0b3a40d9c95f..e44afe37e0eb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,6 +13,7 @@ skip = """
 ./ports/cc3200/hal,\
 ./ports/cc3200/simplelink,\
 ./ports/cc3200/telnet,\
+./ports/esp32/managed_components,\
 ./ports/nrf/drivers/bluetooth/s1*,\
 ./ports/stm32/usbhost,\
 ./tests,\
diff --git a/shared/tinyusb/mp_usbd.c b/shared/tinyusb/mp_usbd.c
index 74b3f074927b..436314dcde5c 100644
--- a/shared/tinyusb/mp_usbd.c
+++ b/shared/tinyusb/mp_usbd.c
@@ -24,42 +24,42 @@
  * THE SOFTWARE.
  */
 
-#include 
-
 #include "py/mpconfig.h"
-#include "py/runtime.h"
 
 #if MICROPY_HW_ENABLE_USBDEV
 
+#include "mp_usbd.h"
+
 #ifndef NO_QSTR
-#include "tusb.h" // TinyUSB is not available when running the string preprocessor
 #include "device/dcd.h"
-#include "device/usbd.h"
-#include "device/usbd_pvt.h"
 #endif
 
-// TinyUSB task function wrapper, as scheduled from the USB IRQ
-static void mp_usbd_task_callback(mp_sched_node_t *node);
-
-extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr);
+#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
 
 void mp_usbd_task(void) {
     tud_task_ext(0, false);
 }
 
+void mp_usbd_task_callback(mp_sched_node_t *node) {
+    (void)node;
+    mp_usbd_task();
+}
+
+#endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
+extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr);
+
 // If -Wl,--wrap=dcd_event_handler is passed to the linker, then this wrapper
 // will be called and allows MicroPython to schedule the TinyUSB task when
 // dcd_event_handler() is called from an ISR.
 TU_ATTR_FAST_FUNC void __wrap_dcd_event_handler(dcd_event_t const *event, bool in_isr) {
-    static mp_sched_node_t usbd_task_node;
-
     __real_dcd_event_handler(event, in_isr);
-    mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback);
+    mp_usbd_schedule_task();
 }
 
-static void mp_usbd_task_callback(mp_sched_node_t *node) {
-    (void)node;
-    mp_usbd_task();
+TU_ATTR_FAST_FUNC void mp_usbd_schedule_task(void) {
+    static mp_sched_node_t usbd_task_node;
+    mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback);
 }
 
 void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) {
@@ -72,4 +72,4 @@ void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) {
     out_str[hex_len] = 0;
 }
 
-#endif
+#endif // MICROPY_HW_ENABLE_USBDEV
diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h
index 89f8bf0ee953..ef3234845139 100644
--- a/shared/tinyusb/mp_usbd.h
+++ b/shared/tinyusb/mp_usbd.h
@@ -27,25 +27,110 @@
 #ifndef MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H
 #define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H
 
+#include "py/mpconfig.h"
+
+#if MICROPY_HW_ENABLE_USBDEV
+
 #include "py/obj.h"
-#include "tusb.h"
+#include "py/objarray.h"
+#include "py/runtime.h"
 
-static inline void mp_usbd_init(void) {
-    // Currently this is a thin wrapper around tusb_init(), however
-    // runtime USB support will require this to be extended.
-    tusb_init();
-}
+#ifndef NO_QSTR
+#include "tusb.h"
+#include "device/dcd.h"
+#endif
 
-// Call this to explicitly run the TinyUSB device task.
+// Run the TinyUSB device task
 void mp_usbd_task(void);
 
+// Schedule a call to mp_usbd_task(), even if no USB interrupt has occurred
+void mp_usbd_schedule_task(void);
+
 // Function to be implemented in port code.
 // Can write a string up to MICROPY_HW_USB_DESC_STR_MAX characters long, plus terminating byte.
 extern void mp_usbd_port_get_serial_number(char *buf);
 
-// Most ports need to write a hexadecimal serial number from a byte array, this
+// Most ports need to write a hexadecimal serial number from a byte array. This
 // is a helper function for this. out_str must be long enough to hold a string of total
 // length (2 * bytes_len + 1) (including NUL terminator).
 void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len);
 
+// Length of built-in configuration descriptor
+#define MP_USBD_BUILTIN_DESC_CFG_LEN (TUD_CONFIG_DESC_LEN +                     \
+    (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) +  \
+    (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0)    \
+    )
+
+// Built-in USB device and configuration descriptor values
+extern const tusb_desc_device_t mp_usbd_builtin_desc_dev;
+extern const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN];
+
+void mp_usbd_task_callback(mp_sched_node_t *node);
+
+#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+void mp_usbd_deinit(void);
+void mp_usbd_init(void);
+
+const char *mp_usbd_runtime_string_cb(uint8_t index);
+
+// Maximum number of pending exceptions per single TinyUSB task execution
+#define MP_USBD_MAX_PEND_EXCS 2
+
+typedef struct {
+    mp_obj_base_t base;
+
+    mp_obj_t desc_dev; // Device descriptor bytes
+    mp_obj_t desc_cfg; // Configuration descriptor bytes
+    mp_obj_t desc_strs; // List/dict/similar to look up string descriptors by index
+
+    // Runtime device driver callback functions
+    mp_obj_t open_itf_cb;
+    mp_obj_t reset_cb;
+    mp_obj_t control_xfer_cb;
+    mp_obj_t xfer_cb;
+
+    mp_obj_t builtin_driver; // Points to one of mp_type_usb_device_builtin_nnn
+
+    bool active; // Has the user set the USB device active?
+    bool trigger; // Has the user requested the active state change (or re-activate)?
+
+    // Temporary pointers for xfer data in progress on each endpoint
+    // Ensuring they aren't garbage collected until the xfer completes
+    mp_obj_t xfer_data[CFG_TUD_ENDPPOINT_MAX][2];
+
+    // Pointer to a memoryview that is reused to refer to various pieces of
+    // control transfer data that are pushed to USB control transfer
+    // callbacks. Python code can't rely on the memoryview contents
+    // to remain valid after the callback returns!
+    mp_obj_array_t *control_data;
+
+    // Pointers to exceptions thrown inside Python callbacks. See
+    // usbd_callback_function_n().
+    mp_uint_t num_pend_excs;
+    mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS];
+} mp_obj_usb_device_t;
+
+// Built-in constant objects, possible values of builtin_driver
+//
+// (Currently not possible to change built-in drivers at runtime, just enable/disable.)
+extern const mp_obj_type_t mp_type_usb_device_builtin_default;
+extern const mp_obj_type_t mp_type_usb_device_builtin_none;
+
+// Return true if any built-in driver is enabled
+inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd) {
+    return usbd->builtin_driver != MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none);
+}
+
+#else // Static USBD drivers only
+
+static inline void mp_usbd_init(void) {
+    // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init()
+    extern bool tusb_init(void);
+    tusb_init();
+}
+
+#endif
+
+#endif // MICROPY_HW_ENABLE_USBDEV
+
 #endif // MICROPY_INCLUDED_SHARED_TINYUSB_USBD_H
diff --git a/shared/tinyusb/mp_usbd_descriptor.c b/shared/tinyusb/mp_usbd_descriptor.c
index 72a265217986..be3473b6b914 100644
--- a/shared/tinyusb/mp_usbd_descriptor.c
+++ b/shared/tinyusb/mp_usbd_descriptor.c
@@ -31,12 +31,11 @@
 
 #include "tusb.h"
 #include "mp_usbd.h"
-#include "mp_usbd_internal.h"
 
 #define USBD_CDC_CMD_MAX_SIZE (8)
 #define USBD_CDC_IN_OUT_MAX_SIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 64)
 
-const tusb_desc_device_t mp_usbd_desc_device_static = {
+const tusb_desc_device_t mp_usbd_builtin_desc_dev = {
     .bLength = sizeof(tusb_desc_device_t),
     .bDescriptorType = TUSB_DESC_DEVICE,
     .bcdUSB = 0x0200,
@@ -53,8 +52,8 @@ const tusb_desc_device_t mp_usbd_desc_device_static = {
     .bNumConfigurations = 1,
 };
 
-const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = {
-    TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_STATIC_MAX, USBD_STR_0, USBD_STATIC_DESC_LEN,
+const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN] = {
+    TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_BUILTIN_MAX, USBD_STR_0, MP_USBD_BUILTIN_DESC_CFG_LEN,
         0, USBD_MAX_POWER_MA),
 
     #if CFG_TUD_CDC
@@ -69,51 +68,68 @@ const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = {
 const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
     char serial_buf[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes terminating NUL byte
     static uint16_t desc_wstr[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes prefix uint16_t
-    const char *desc_str;
+    const char *desc_str = NULL;
     uint16_t desc_len;
 
-    switch (index) {
-        case 0:
-            desc_wstr[1] = 0x0409; // supported language is English
-            desc_len = 4;
-            break;
-        case USBD_STR_SERIAL:
-            // TODO: make a port-specific serial number callback
-            mp_usbd_port_get_serial_number(serial_buf);
-            desc_str = serial_buf;
-            break;
-        case USBD_STR_MANUF:
-            desc_str = MICROPY_HW_USB_MANUFACTURER_STRING;
-            break;
-        case USBD_STR_PRODUCT:
-            desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING;
-            break;
-        #if CFG_TUD_CDC
-        case USBD_STR_CDC:
-            desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING;
-            break;
-        #endif
-        #if CFG_TUD_MSC
-        case USBD_STR_MSC:
-            desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING;
-            break;
-        #endif
-        default:
-            desc_str = NULL;
-    }
+    #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+    desc_str = mp_usbd_runtime_string_cb(index);
+    #endif
 
-    if (index != 0) {
+    if (index == 0) {
+        // String descriptor 0 is special, see USB 2.0 section 9.6.7 String
+        //
+        // Expect any runtime value in desc_str to be a fully formed descriptor
         if (desc_str == NULL) {
-            return NULL; // Will STALL the endpoint
+            desc_str = "\x04\x03\x09\x04"; // Descriptor for "English"
         }
+        if (desc_str[0] < sizeof(desc_wstr)) {
+            memcpy(desc_wstr, desc_str, desc_str[0]);
+            return desc_wstr;
+        }
+        return NULL; // Descriptor length too long (or malformed), stall endpoint
+    }
 
-        // Convert from narrow string to wide string
-        desc_len = 2;
-        for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) {
-            desc_wstr[1 + i] = desc_str[i];
-            desc_len += 2;
+    // Otherwise, generate a "UNICODE" string descriptor from the C string
+
+    if (desc_str == NULL) {
+        // Fall back to the "static" string
+        switch (index) {
+            case USBD_STR_SERIAL:
+                mp_usbd_port_get_serial_number(serial_buf);
+                desc_str = serial_buf;
+                break;
+            case USBD_STR_MANUF:
+                desc_str = MICROPY_HW_USB_MANUFACTURER_STRING;
+                break;
+            case USBD_STR_PRODUCT:
+                desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING;
+                break;
+            #if CFG_TUD_CDC
+            case USBD_STR_CDC:
+                desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING;
+                break;
+            #endif
+            #if CFG_TUD_MSC
+            case USBD_STR_MSC:
+                desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING;
+                break;
+            #endif
+            default:
+                break;
         }
     }
+
+    if (desc_str == NULL) {
+        return NULL; // No string, STALL the endpoint
+    }
+
+    // Convert from narrow string to wide string
+    desc_len = 2;
+    for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) {
+        desc_wstr[1 + i] = desc_str[i];
+        desc_len += 2;
+    }
+
     // first byte is length (including header), second byte is string type
     desc_wstr[0] = (TUSB_DESC_STRING << 8) | desc_len;
 
@@ -121,13 +137,21 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
 }
 
 
+#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
 const uint8_t *tud_descriptor_device_cb(void) {
-    return (const void *)&mp_usbd_desc_device_static;
+    return (const void *)&mp_usbd_builtin_desc_dev;
 }
 
 const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
     (void)index;
-    return mp_usbd_desc_cfg_static;
+    return mp_usbd_builtin_desc_cfg;
 }
 
-#endif
+#else
+
+// If runtime device support is enabled, descriptor callbacks are implemented in usbd.c
+
+#endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
+#endif // MICROPY_HW_ENABLE_USBDEV
diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c
new file mode 100644
index 000000000000..0b4c79831bec
--- /dev/null
+++ b/shared/tinyusb/mp_usbd_runtime.c
@@ -0,0 +1,558 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2022 Blake W. Felt
+ * Copyright (c) 2022-2023 Angus Gratton
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include 
+
+#include "mp_usbd.h"
+#include "py/mpconfig.h"
+#include "py/mperrno.h"
+#include "py/mphal.h"
+#include "py/obj.h"
+#include "py/objarray.h"
+#include "py/objstr.h"
+#include "py/runtime.h"
+
+#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
+#ifndef NO_QSTR
+#include "tusb.h" // TinyUSB is not available when running the string preprocessor
+#include "device/dcd.h"
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+#endif
+
+static bool in_usbd_task; // Flags if mp_usbd_task() is currently running
+
+// Some top-level functions that manage global TinyUSB USBD state, not the
+// singleton object visible to Python
+static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd);
+static void mp_usbd_task_inner(void);
+
+// Pend an exception raise in a USBD callback to print when safe.
+//
+// We can't raise any exceptions out of the TinyUSB task, as it may still need
+// to do some state cleanup.
+//
+// The requirement for this becomes very similar to
+// mp_call_function_x_protected() for interrupts, but it's more restrictive: if
+// the C-based USB-CDC serial port is in use, we can't print from inside a
+// TinyUSB callback as it might try to recursively call into TinyUSB to flush
+// the CDC port and make room. Therefore, we have to store the exception and
+// print it as we exit the TinyUSB task.
+//
+// (Worse, a single TinyUSB task can process multiple callbacks and therefore generate
+// multiple exceptions...)
+static void usbd_pend_exception(mp_obj_t exception) {
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    assert(usbd != NULL);
+    if (usbd->num_pend_excs < MP_USBD_MAX_PEND_EXCS) {
+        usbd->pend_excs[usbd->num_pend_excs] = exception;
+    }
+    usbd->num_pend_excs++;
+}
+
+// Call a Python function from inside a TinyUSB callback.
+//
+// Handles any exception using usbd_pend_exception()
+static mp_obj_t usbd_callback_function_n(mp_obj_t fun, size_t n_args, const mp_obj_t *args) {
+    nlr_buf_t nlr;
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t ret = mp_call_function_n_kw(fun, n_args, 0, args);
+        nlr_pop();
+        return ret;
+    } else {
+        usbd_pend_exception(MP_OBJ_FROM_PTR(nlr.ret_val));
+        return MP_OBJ_NULL;
+    }
+}
+
+// Return a pointer to the data inside a Python buffer provided in a callback
+static void *usbd_get_buffer_in_cb(mp_obj_t obj, mp_uint_t flags) {
+    mp_buffer_info_t buf_info;
+    if (obj == mp_const_none) {
+        // This is only if the user somehow
+        return NULL;
+    } else if (mp_get_buffer(obj, &buf_info, flags)) {
+        return buf_info.buf;
+    } else {
+        mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_TypeError,
+            MP_ERROR_TEXT("object with buffer protocol required"));
+        usbd_pend_exception(exc);
+        return NULL;
+    }
+}
+
+const uint8_t *tud_descriptor_device_cb(void) {
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    const void *result = NULL;
+    if (usbd) {
+        result = usbd_get_buffer_in_cb(usbd->desc_dev, MP_BUFFER_READ);
+    }
+    return result ? result : &mp_usbd_builtin_desc_dev;
+}
+
+const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
+    (void)index;
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    const void *result = NULL;
+    if (usbd) {
+        result = usbd_get_buffer_in_cb(usbd->desc_cfg, MP_BUFFER_READ);
+    }
+    return result ? result : &mp_usbd_builtin_desc_cfg;
+}
+
+const char *mp_usbd_runtime_string_cb(uint8_t index) {
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    nlr_buf_t nlr;
+
+    if (usbd == NULL || usbd->desc_strs == mp_const_none) {
+        return NULL;
+    }
+
+    if (nlr_push(&nlr) == 0) {
+        mp_obj_t res = mp_obj_subscr(usbd->desc_strs, mp_obj_new_int(index), MP_OBJ_SENTINEL);
+        nlr_pop();
+        if (res != mp_const_none) {
+            return usbd_get_buffer_in_cb(res, MP_BUFFER_READ);
+        }
+    } else {
+        mp_obj_t exception = MP_OBJ_FROM_PTR(nlr.ret_val);
+        if (!(mp_obj_is_type(exception, &mp_type_KeyError) || mp_obj_is_type(exception, &mp_type_IndexError))) {
+            // Don't print KeyError or IndexError, allowing dicts or lists to have missing entries.
+            // but log any more exotic errors that pop up
+            usbd_pend_exception(exception);
+        }
+    }
+
+    return NULL;
+}
+
+bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
+    return false; // Currently no support for Vendor control transfers on the Python side
+}
+
+// Generic "runtime device" TinyUSB class driver, delegates everything to Python callbacks
+
+static void runtime_dev_init(void) {
+}
+
+static void runtime_dev_reset(uint8_t rhport) {
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    if (!usbd) {
+        return;
+    }
+
+    for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) {
+        for (int dir = 0; dir < 2; dir++) {
+            usbd->xfer_data[epnum][dir] = mp_const_none;
+        }
+    }
+
+    if (mp_obj_is_callable(usbd->reset_cb)) {
+        usbd_callback_function_n(usbd->reset_cb, 0, NULL);
+    }
+}
+
+// Calculate how many interfaces TinyUSB expects us to claim from
+// driver open().
+//
+// Annoyingly, the calling function (process_set_config() in TinyUSB) knows
+// this but doesn't pass the information to us.
+//
+// The answer is:
+// - If an Interface Association Descriptor (IAD) is immediately before itf_desc
+//   in the configuration descriptor, then claim all of the associated interfaces.
+// - Otherwise, claim exactly one interface
+//
+// Relying on the implementation detail that itf_desc is a pointer inside the
+// tud_descriptor_configuration_cb() result. Therefore, we can iterate through
+// from the beginning to check for an IAD immediately preceding it.
+//
+// Returns the number of associated interfaces to claim.
+static uint8_t _runtime_dev_count_itfs(tusb_desc_interface_t const *itf_desc) {
+    const tusb_desc_configuration_t *cfg_desc = (const void *)tud_descriptor_configuration_cb(0);
+    const uint8_t *p_desc = (const void *)cfg_desc;
+    const uint8_t *p_end = p_desc + cfg_desc->wTotalLength;
+    assert(p_desc <= itf_desc && itf_desc < p_end);
+    while (p_desc != (const void *)itf_desc && p_desc < p_end) {
+        const uint8_t *next = tu_desc_next(p_desc);
+
+        if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION
+            && next == (const void *)itf_desc) {
+            const tusb_desc_interface_assoc_t *desc_iad = (const void *)p_desc;
+            return desc_iad->bInterfaceCount;
+        }
+        p_desc = next;
+    }
+    return 1; // No IAD found
+}
+
+// Scan the other descriptors after these interface(s) to find the total associated length to claim
+// from driver open().
+//
+// Total number of interfaces to scan for is assoc_itf_count.
+//
+// Opens any associated endpoints so they can have transfers submitted against them.
+//
+// Returns the total number of descriptor bytes to claim.
+static uint16_t _runtime_dev_claim_itfs(tusb_desc_interface_t const *itf_desc, uint8_t assoc_itf_count, uint16_t max_len) {
+    const uint8_t *p_desc = (const void *)itf_desc;
+    const uint8_t *p_end = p_desc + max_len;
+    while (p_desc < p_end) {
+        if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) {
+            if (assoc_itf_count > 0) {
+                // Claim this interface descriptor
+                assoc_itf_count--;
+            } else {
+                // This is the end of the previous interface's associated descriptors
+                break;
+            }
+        } else if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
+            // Open any endpoints that we come across
+            if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
+                bool r = usbd_edpt_open(USBD_RHPORT, (const void *)p_desc);
+                if (!r) {
+                    mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(MP_ENODEV));
+                    usbd_pend_exception(exc);
+                    break;
+                }
+            }
+        }
+        p_desc = tu_desc_next(p_desc);
+    }
+    return p_desc - (const uint8_t *)itf_desc;
+}
+
+// TinyUSB "Application driver" open callback. Called when the USB host sets
+// configuration. Returns number of bytes to claim from descriptors pointed to
+// by itf_desc.
+//
+// This is a little fiddly as it's called before any built-in TinyUSB drivers,
+// but we don't want to override those.
+//
+// Also, TinyUSB expects us to know how many interfaces to claim for each time
+// this function is called, and will behave unexpectedly if we claim the wrong
+// number of interfaces. However, unlike a "normal" USB driver we don't know at
+// compile time how many interfaces we've implemented. Instead, we have to look
+// back through the configuration descriptor to figure this out.
+static uint16_t runtime_dev_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) {
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+
+    // Runtime USB isn't initialised
+    if (!usbd) {
+        return 0;
+    }
+
+    // If TinyUSB built-in drivers are enabled, don't claim any interface in the built-in range
+    if (mp_usb_device_builtin_enabled(usbd) && itf_desc->bInterfaceNumber < USBD_ITF_BUILTIN_MAX) {
+        return 0;
+    }
+
+    // Determine the total descriptor length of the interface(s) we are going to claim
+    uint8_t assoc_itf_count = _runtime_dev_count_itfs(itf_desc);
+    uint16_t claim_len = _runtime_dev_claim_itfs(itf_desc, assoc_itf_count, max_len);
+
+    // Call the Python callback to allow the driver to start working with these interface(s)
+
+    if (mp_obj_is_callable(usbd->open_itf_cb)) {
+        // Repurpose the control_data memoryview to point into itf_desc for this one call
+        usbd->control_data->items = (void *)itf_desc;
+        usbd->control_data->len = claim_len;
+        mp_obj_t args[] = { MP_OBJ_FROM_PTR(usbd->control_data) };
+        usbd_callback_function_n(usbd->open_itf_cb, 1, args);
+        usbd->control_data->len = 0;
+        usbd->control_data->items = NULL;
+    }
+
+    return claim_len;
+}
+
+static bool runtime_dev_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
+    mp_obj_t cb_res = mp_const_false;
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    tusb_dir_t dir = request->bmRequestType_bit.direction;
+    mp_buffer_info_t buf_info;
+
+    if (!usbd) {
+        return false;
+    }
+
+    if (mp_obj_is_callable(usbd->control_xfer_cb)) {
+        usbd->control_data->items = (void *)request;
+        usbd->control_data->len = sizeof(tusb_control_request_t);
+        mp_obj_t args[] = {
+            mp_obj_new_int(stage),
+            MP_OBJ_FROM_PTR(usbd->control_data),
+        };
+        cb_res = usbd_callback_function_n(usbd->control_xfer_cb, MP_ARRAY_SIZE(args), args);
+        usbd->control_data->items = NULL;
+        usbd->control_data->len = 0;
+
+        if (cb_res == MP_OBJ_NULL) {
+            // Exception occurred in the callback handler, stall this transfer
+            cb_res = mp_const_false;
+        }
+    }
+
+    // Check if callback returned any data to submit
+    if (mp_get_buffer(cb_res, &buf_info, dir == TUSB_DIR_IN ? MP_BUFFER_READ : MP_BUFFER_RW)) {
+        bool result = tud_control_xfer(USBD_RHPORT,
+            request,
+            buf_info.buf,
+            buf_info.len);
+
+        if (result) {
+            // Keep buffer object alive until the transfer completes
+            usbd->xfer_data[0][dir] = cb_res;
+        }
+
+        return result;
+    } else {
+        // Expect True or False to stall or continue
+
+        if (stage == CONTROL_STAGE_ACK) {
+            // Allow data to be GCed once it's no longer in use
+            usbd->xfer_data[0][dir] = mp_const_none;
+        }
+        return mp_obj_is_true(cb_res);
+    }
+}
+
+static bool runtime_dev_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+    mp_obj_t ep = mp_obj_new_int(ep_addr);
+    mp_obj_t cb_res = mp_const_false;
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    if (!usbd) {
+        return false;
+    }
+
+    if (mp_obj_is_callable(usbd->xfer_cb)) {
+        mp_obj_t args[] = {
+            ep,
+            MP_OBJ_NEW_SMALL_INT(result),
+            MP_OBJ_NEW_SMALL_INT(xferred_bytes),
+        };
+        cb_res = usbd_callback_function_n(usbd->xfer_cb, MP_ARRAY_SIZE(args), args);
+    }
+
+    // Clear any xfer_data for this endpoint
+    usbd->xfer_data[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = mp_const_none;
+
+    return cb_res != MP_OBJ_NULL && mp_obj_is_true(cb_res);
+}
+
+static usbd_class_driver_t const _runtime_dev_driver =
+{
+    #if CFG_TUSB_DEBUG >= 2
+    .name = "runtime_dev",
+    #endif
+    .init = runtime_dev_init,
+    .reset = runtime_dev_reset,
+    .open = runtime_dev_open,
+    .control_xfer_cb = runtime_dev_control_xfer_cb,
+    .xfer_cb = runtime_dev_xfer_cb,
+    .sof = NULL
+};
+
+usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) {
+    *driver_count = 1;
+    return &_runtime_dev_driver;
+}
+
+
+// Functions below here (named mp_usbd_xyz) apply to the whole TinyUSB C-based subsystem
+// and not necessarily the USBD singleton object (named usbd_xyz).
+
+// To support soft reset clearing USB runtime state, we manage three TinyUSB states:
+//
+// - "Not initialised" - tusb_inited() returns false, no USB at all). Only way
+//   back to this state is hard reset.
+//
+// - "Activated" - tusb_inited() returns true, USB device "connected" at device
+//   end and available to host.
+//
+// - "Deactivated" - tusb_inited() returns true, but USB device "disconnected"
+//   at device end and host can't see it.
+
+
+// Top-level USB device subsystem init.
+//
+// Initialises TinyUSB and/or re-activates it, provided USB is needed.
+//
+// This is called on any soft reset after boot.py runs, or on demand if the
+// user activates USB and it hasn't activated yet.
+void mp_usbd_init(void) {
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    bool need_usb;
+
+    if (usbd == NULL) {
+        // No runtime USB device
+        #if CFG_TUD_CDC || CFG_TUD_MSC
+        // Builtin  drivers are available, so initialise as defaults
+        need_usb = true;
+        #else
+        // No builtin drivers, nothing to initialise
+        need_usb = false;
+        #endif
+    } else {
+        // Otherwise, initialise based on whether runtime USB is active
+        need_usb = usbd->active;
+    }
+
+    if (need_usb) {
+        tusb_init(); // Safe to call redundantly
+        tud_connect(); // Reconnect if mp_usbd_deinit() has disconnected
+    }
+}
+
+// Top-level USB device deinit.
+//
+// This variant is called from soft reset, NULLs out the USB device
+// singleton instance from MP_STATE_VM, and disconnects the port if a
+// runtime device was active.
+void mp_usbd_deinit(void) {
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+    MP_STATE_VM(usbd) = MP_OBJ_NULL;
+    if (usbd && usbd->active) {
+        // Disconnect if a runtime USB device was active
+        mp_usbd_disconnect(usbd);
+    }
+}
+
+// Thin wrapper around tud_disconnect() that tells TinyUSB all endpoints
+// have stalled, to prevent it getting confused if a transfer is in progress.
+static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd) {
+    if (!tusb_inited()) {
+        return; // TinyUSB hasn't initialised
+    }
+
+    if (usbd) {
+        // There might be USB transfers in progress right now, so need to stall any live
+        // endpoints
+        //
+        // TODO: figure out if we really need this
+        for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) {
+            for (int dir = 0; dir < 2; dir++) {
+                if (usbd->xfer_data[epnum][dir] != mp_const_none) {
+                    usbd_edpt_stall(USBD_RHPORT, tu_edpt_addr(epnum, dir));
+                    usbd->xfer_data[epnum][dir] = mp_const_none;
+                }
+            }
+        }
+    }
+
+    #if MICROPY_HW_USB_CDC
+    // Ensure no pending static CDC writes, as these can cause TinyUSB to crash
+    tud_cdc_write_clear();
+    #endif
+
+    bool was_connected = tud_connected();
+    tud_disconnect();
+    if (was_connected) {
+        // Need to ensure a long enough delay before TinyUSB re-connects that
+        // the host triggers a bus reset. This may happen anyway, but delaying here
+        // lets us be "belt and braces" sure.
+        mp_hal_delay_ms(50);
+    }
+}
+
+// Thjs callback is queued by mp_usbd_schedule_task() to process USB later.
+void mp_usbd_task_callback(mp_sched_node_t *node) {
+    if (tud_inited() && !in_usbd_task) {
+        mp_usbd_task_inner();
+    }
+    // If in_usbd_task is set, it means something else has already manually called
+    // mp_usbd_task() (most likely: C-based USB-CDC serial port). Now the MP
+    // scheduler is running inside there and triggering this callback. It's OK
+    // to skip, the already-running outer TinyUSB task will process all pending
+    // events before it returns.
+}
+
+// Task function can be called manually to force processing of USB events
+// (mostly from USB-CDC serial port when blocking.)
+void mp_usbd_task(void) {
+    if (in_usbd_task) {
+        // If this exception triggers, it means a USB callback tried to do
+        // something that itself became blocked on TinyUSB (most likely: read or
+        // write from a C-based USB-CDC serial port.)
+        mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TinyUSB callback can't recurse"));
+    }
+
+    mp_usbd_task_inner();
+}
+
+static void mp_usbd_task_inner(void) {
+    in_usbd_task = true;
+
+    tud_task_ext(0, false);
+
+    mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+
+    // Check for a triggered change to/from active state
+    if (usbd && usbd->trigger) {
+        if (usbd->active) {
+            if (tud_connected()) {
+                // If a SETUP packet has been received, first disconnect
+                // and wait for the host to recognise this and trigger a bus reset.
+                //
+                // Effectively this forces it to re-enumerate the device.
+                mp_usbd_disconnect(usbd);
+            }
+            tud_connect();
+        } else {
+            mp_usbd_disconnect(usbd);
+        }
+        usbd->trigger = false;
+    }
+
+    in_usbd_task = false;
+
+    if (usbd) {
+        // Print any exceptions that were raised by Python callbacks
+        // inside tud_task_ext(). See usbd_callback_function_n.
+
+        // As printing exceptions to USB-CDC may recursively call mp_usbd_task(),
+        // first copy out the pending data to the local stack
+        mp_uint_t num_pend_excs = usbd->num_pend_excs;
+        mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS];
+        for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) {
+            pend_excs[i] = usbd->pend_excs[i];
+            usbd->pend_excs[i] = mp_const_none;
+        }
+        usbd->num_pend_excs = 0;
+
+        // Now print the exceptions stored from this mp_usbd_task() call
+        for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) {
+            mp_obj_print_exception(&mp_plat_print, pend_excs[i]);
+        }
+        if (num_pend_excs > MP_USBD_MAX_PEND_EXCS) {
+            mp_printf(&mp_plat_print, "%u additional exceptions in USB callbacks\n",
+                num_pend_excs - MP_USBD_MAX_PEND_EXCS);
+        }
+    }
+}
+
+#endif // MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h
index 266cb88cc2fe..ab47321afdc0 100644
--- a/shared/tinyusb/tusb_config.h
+++ b/shared/tinyusb/tusb_config.h
@@ -31,6 +31,10 @@
 
 #if MICROPY_HW_ENABLE_USBDEV
 
+#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE 0
+#endif
+
 #ifndef MICROPY_HW_USB_MANUFACTURER_STRING
 #define MICROPY_HW_USB_MANUFACTURER_STRING "MicroPython"
 #endif
@@ -86,12 +90,9 @@
 #define CFG_TUD_MSC_BUFSIZE (MICROPY_FATFS_MAX_SS)
 #endif
 
-// Define static descriptor size and interface count based on the above config
+#define USBD_RHPORT (0) // Currently only one port is supported
 
-#define USBD_STATIC_DESC_LEN (TUD_CONFIG_DESC_LEN +                     \
-    (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) +  \
-    (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0)    \
-    )
+// Define built-in interface, string and endpoint numbering based on the above config
 
 #define USBD_STR_0 (0x00)
 #define USBD_STR_MANUF (0x01)
@@ -103,7 +104,7 @@
 #define USBD_MAX_POWER_MA (250)
 
 #ifndef MICROPY_HW_USB_DESC_STR_MAX
-#define MICROPY_HW_USB_DESC_STR_MAX (20)
+#define MICROPY_HW_USB_DESC_STR_MAX (40)
 #endif
 
 #if CFG_TUD_CDC
@@ -126,19 +127,19 @@
 #endif // CFG_TUD_CDC
 #endif // CFG_TUD_MSC
 
-/* Limits of statically defined USB interfaces, endpoints, strings */
+/* Limits of builtin USB interfaces, endpoints, strings */
 #if CFG_TUD_MSC
-#define USBD_ITF_STATIC_MAX (USBD_ITF_MSC + 1)
-#define USBD_STR_STATIC_MAX (USBD_STR_MSC + 1)
-#define USBD_EP_STATIC_MAX (EPNUM_MSC_OUT + 1)
+#define USBD_ITF_BUILTIN_MAX (USBD_ITF_MSC + 1)
+#define USBD_STR_BUILTIN_MAX (USBD_STR_MSC + 1)
+#define USBD_EP_BUILTIN_MAX (EPNUM_MSC_OUT + 1)
 #elif CFG_TUD_CDC
-#define USBD_ITF_STATIC_MAX (USBD_ITF_CDC + 2)
-#define USBD_STR_STATIC_MAX (USBD_STR_CDC + 1)
-#define USBD_EP_STATIC_MAX (((EPNUM_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1)
+#define USBD_ITF_BUILTIN_MAX (USBD_ITF_CDC + 2)
+#define USBD_STR_BUILTIN_MAX (USBD_STR_CDC + 1)
+#define USBD_EP_BUILTIN_MAX (((USBD_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1)
 #else // !CFG_TUD_MSC && !CFG_TUD_CDC
-#define USBD_ITF_STATIC_MAX (0)
-#define USBD_STR_STATIC_MAX (0)
-#define USBD_EP_STATIC_MAX (0)
+#define USBD_ITF_BUILTIN_MAX (0)
+#define USBD_STR_BUILTIN_MAX (0)
+#define USBD_EP_BUILTIN_MAX (0)
 #endif
 
 #endif // MICROPY_HW_ENABLE_USBDEV
diff --git a/tests/basics/deque1.py b/tests/basics/deque1.py
index 8b7874e2b1d3..188069bcbf30 100644
--- a/tests/basics/deque1.py
+++ b/tests/basics/deque1.py
@@ -63,3 +63,52 @@
     ~d
 except TypeError:
     print("TypeError")
+
+
+# Same tests, but now with pop() and appendleft()
+
+d = deque((), 2)
+print(len(d))
+print(bool(d))
+
+try:
+    d.popleft()
+except IndexError:
+    print("IndexError")
+
+print(d.append(1))
+print(len(d))
+print(bool(d))
+print(d.popleft())
+print(len(d))
+
+d.append(2)
+print(d.popleft())
+
+d.append(3)
+d.append(4)
+print(len(d))
+print(d.popleft(), d.popleft())
+try:
+    d.popleft()
+except IndexError:
+    print("IndexError")
+
+d.append(5)
+d.append(6)
+d.append(7)
+print(len(d))
+print(d.popleft(), d.popleft())
+print(len(d))
+try:
+    d.popleft()
+except IndexError:
+    print("IndexError")
+
+d = deque((), 2)
+d.appendleft(1)
+d.appendleft(2)
+d.appendleft(3)
+d.appendleft(4)
+d.appendleft(5)
+print(d.pop(), d.pop())
\ No newline at end of file
diff --git a/tests/basics/deque2.py b/tests/basics/deque2.py
index 80fcd6678566..3552d5be37ab 100644
--- a/tests/basics/deque2.py
+++ b/tests/basics/deque2.py
@@ -1,63 +1,43 @@
-# Tests for deques with "check overflow" flag and other extensions
-# wrt to CPython.
 try:
     from collections import deque
 except ImportError:
     print("SKIP")
     raise SystemExit
 
+# Initial sequence is supported
+d = deque([1, 2, 3], 10)
 
-# Initial sequence is not supported
-try:
-    deque([1, 2, 3], 10)
-except ValueError:
-    print("ValueError")
-
-# Not even empty list, only empty tuple
-try:
-    deque([], 10)
-except ValueError:
-    print("ValueError")
+# iteration over sequence
+for x in d:
+    print(x)
 
-# Only fixed-size deques are supported, so length arg is mandatory
-try:
-    deque(())
-except TypeError:
-    print("TypeError")
+# Iterables larger than maxlen have the beginnings removed, also tests
+# iteration through conversion to list
+d = deque([1, 2, 3, 4, 5], 3)
+print(list(d))
 
-d = deque((), 2, True)
+# Empty iterables are also supported
+deque([], 10)
 
-try:
-    d.popleft()
-except IndexError:
-    print("IndexError")
+# Extending deques with iterables
+d.extend([6, 7])
+print(list(d))
 
-print(d.append(1))
-print(d.popleft())
+# Accessing queue elements via index
+d = deque((0, 1, 2, 3), 5)
+print(d[0], d[1], d[-1])
 
-d.append(2)
-print(d.popleft())
+# Writing queue elements via index
+d[3] = 5
+print(d[3])
 
-d.append(3)
-d.append(4)
-print(d.popleft(), d.popleft())
+# Accessing indices out of bounds raises IndexError
 try:
-    d.popleft()
-except IndexError as e:
-    print(repr(e))
-
-d.append(5)
-d.append(6)
-print(len(d))
-try:
-    d.append(7)
-except IndexError as e:
-    print(repr(e))
-print(len(d))
+    d[4]
+except IndexError:
+    print("IndexError")
 
-print(d.popleft(), d.popleft())
-print(len(d))
 try:
-    d.popleft()
-except IndexError as e:
-    print(repr(e))
+    d[4] = 0
+except IndexError:
+    print("IndexError")
diff --git a/tests/basics/deque_micropython.py b/tests/basics/deque_micropython.py
new file mode 100644
index 000000000000..5f32bbc496a3
--- /dev/null
+++ b/tests/basics/deque_micropython.py
@@ -0,0 +1,75 @@
+# Test MicroPython-specific features of collections.deque.
+
+try:
+    from collections import deque
+except ImportError:
+    print("SKIP")
+    raise SystemExit
+
+
+# Only fixed-size deques are supported, so length arg is mandatory.
+try:
+    deque(())
+except TypeError:
+    print("TypeError")
+
+# Test third argument: flags when True means check for under/overflow
+d = deque((), 2, True)
+
+try:
+    d.popleft()
+except IndexError:
+    print("IndexError")
+
+try:
+    d.pop()
+except IndexError:
+    print("IndexError")
+
+# Removing elements with del is not supported, fallback to
+# mp_obj_subscr() error message.
+try:
+    del d[0]
+except TypeError:
+    print("TypeError")
+
+print(d.append(1))
+print(d.popleft())
+
+d.append(2)
+print(d.popleft())
+
+d.append(3)
+d.append(4)
+print(d.popleft(), d.popleft())
+try:
+    d.popleft()
+except IndexError as e:
+    print(repr(e))
+
+try:
+    d.pop()
+except IndexError as e:
+    print(repr(e))
+
+d.append(5)
+d.append(6)
+print(len(d))
+try:
+    d.append(7)
+except IndexError as e:
+    print(repr(e))
+
+try:
+    d.appendleft(8)
+except IndexError as e:
+    print(repr(e))
+
+print(len(d))
+
+print(d.popleft(), d.popleft())
+print(len(d))
+try:
+    d.popleft()
+except IndexError as e:
+    print(repr(e))
diff --git a/tests/basics/deque2.py.exp b/tests/basics/deque_micropython.py.exp
similarity index 63%
rename from tests/basics/deque2.py.exp
rename to tests/basics/deque_micropython.py.exp
index 3df8acf40562..f1ff7b77ac88 100644
--- a/tests/basics/deque2.py.exp
+++ b/tests/basics/deque_micropython.py.exp
@@ -1,14 +1,16 @@
-ValueError
-ValueError
 TypeError
 IndexError
+IndexError
+TypeError
 None
 1
 2
 3 4
 IndexError('empty',)
+IndexError('empty',)
 2
 IndexError('full',)
+IndexError('full',)
 2
 5 6
 0
diff --git a/tests/basics/deque_slice.py b/tests/basics/deque_slice.py
new file mode 100644
index 000000000000..367aeea3a171
--- /dev/null
+++ b/tests/basics/deque_slice.py
@@ -0,0 +1,29 @@
+try:
+    from collections import deque
+except ImportError:
+    print("SKIP")
+    raise SystemExit
+
+d = deque((), 10)
+
+d.append(1)
+d.append(2)
+d.append(3)
+
+# Index slicing for reads is not supported in CPython
+try:
+    d[0:1]
+except TypeError:
+    print("TypeError")
+
+# Index slicing for writes is not supported in CPython
+try:
+    d[0:1] = (-1, -2)
+except TypeError:
+    print("TypeError")
+
+# Index slicing for writes is not supported in CPython
+try:
+    del d[0:1]
+except TypeError:
+    print("TypeError")
diff --git a/tests/float/float_struct.py b/tests/float/float_struct.py
index 47fe40501803..6e282a6afe3e 100644
--- a/tests/float/float_struct.py
+++ b/tests/float/float_struct.py
@@ -8,7 +8,7 @@
 i = 1.0 + 1 / 2
 # TODO: it looks like '=' format modifier is not yet supported
 # for fmt in ('f', 'd', '>f', '>d', 'f", ">d", "e", ">f", ">d", "\xa38\x01:\xff",
+    0x1006: b"M\x06\x13\x1f\x02\x004build/features0.native.mpy\x00\x12factorial\x00\x88\x02\x18\xe0\x00\x00\x10\xb5\tK\tJ{D\x9cX\x02!\xe3h\x98G\x03\x00\x01 \x00+\x02\xd0XC\x01;\xfa\xe7\x02!#i\x98G\x10\xbd\xc0Fj\x00\x00\x00\x00\x00\x00\x00\xf8\xb5\nN\nK~D\xf4XChgiXh\xb8G\x05\x00\x07K\x08I\xf3XyDX\x88ck\x98G(\x00\xb8G h\xf8\xbd\xc0F:\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x11<\r>\xa98\x01:\xff",
 }
 
 # Populate armv7m-derived archs based on armv6m.
diff --git a/tests/micropython/viper_error.py b/tests/micropython/viper_error.py
index 80617af0c1f2..6c5c3ba20070 100644
--- a/tests/micropython/viper_error.py
+++ b/tests/micropython/viper_error.py
@@ -50,6 +50,9 @@ def f():
 # incorrect return type
 test("@micropython.viper\ndef f() -> int: return []")
 
+# can't do unary op of incompatible type
+test("@micropython.viper\ndef f(x:ptr): -x")
+
 # can't do binary op between incompatible types
 test("@micropython.viper\ndef f(): 1 + []")
 test("@micropython.viper\ndef f(x:int, y:uint): x < y")
@@ -69,9 +72,7 @@ def f():
 test("@micropython.viper\ndef f(): raise 1")
 
 # unary ops not implemented
-test("@micropython.viper\ndef f(x:int): +x")
-test("@micropython.viper\ndef f(x:int): -x")
-test("@micropython.viper\ndef f(x:int): ~x")
+test("@micropython.viper\ndef f(x:int): not x")
 
 # binary op not implemented
 test("@micropython.viper\ndef f(x:uint, y:uint): res = x // y")
diff --git a/tests/micropython/viper_error.py.exp b/tests/micropython/viper_error.py.exp
index 31c85b1d872a..51cbd6c7097c 100644
--- a/tests/micropython/viper_error.py.exp
+++ b/tests/micropython/viper_error.py.exp
@@ -5,6 +5,7 @@ ViperTypeError("local 'x' used before type known",)
 ViperTypeError("local 'x' has type 'int' but source is 'object'",)
 ViperTypeError("can't implicitly convert 'ptr' to 'bool'",)
 ViperTypeError("return expected 'int' but got 'object'",)
+ViperTypeError("can't do unary op of 'ptr'",)
 ViperTypeError("can't do binary op between 'int' and 'object'",)
 ViperTypeError('comparison of int and uint',)
 ViperTypeError("can't load from 'int'",)
@@ -15,9 +16,7 @@ ViperTypeError("can't store to 'int'",)
 ViperTypeError("can't store 'None'",)
 ViperTypeError("can't store 'None'",)
 ViperTypeError('must raise an object',)
-ViperTypeError('unary op __pos__ not implemented',)
-ViperTypeError('unary op __neg__ not implemented',)
-ViperTypeError('unary op __invert__ not implemented',)
+ViperTypeError("'not' not implemented",)
 ViperTypeError('div/mod not implemented for uint',)
 ViperTypeError('div/mod not implemented for uint',)
 ViperTypeError('binary op  not implemented',)
diff --git a/tests/micropython/viper_unop.py b/tests/micropython/viper_unop.py
new file mode 100644
index 000000000000..61cbd5125f16
--- /dev/null
+++ b/tests/micropython/viper_unop.py
@@ -0,0 +1,31 @@
+# test unary operators
+
+
+@micropython.viper
+def pos(x: int) -> int:
+    return +x
+
+
+print(pos(0))
+print(pos(1))
+print(pos(-2))
+
+
+@micropython.viper
+def neg(x: int) -> int:
+    return -x
+
+
+print(neg(0))
+print(neg(1))
+print(neg(-2))
+
+
+@micropython.viper
+def inv(x: int) -> int:
+    return ~x
+
+
+print(inv(0))
+print(inv(1))
+print(inv(-2))
diff --git a/tests/micropython/viper_unop.py.exp b/tests/micropython/viper_unop.py.exp
new file mode 100644
index 000000000000..6d93312caa13
--- /dev/null
+++ b/tests/micropython/viper_unop.py.exp
@@ -0,0 +1,9 @@
+0
+1
+-2
+0
+-1
+2
+-1
+-2
+1
diff --git a/tests/ports/webassembly/basic.js b/tests/ports/webassembly/basic.js
new file mode 100644
index 000000000000..19b1881faf5c
--- /dev/null
+++ b/tests/ports/webassembly/basic.js
@@ -0,0 +1,8 @@
+import(process.argv[2]).then((mp) => {
+    mp.loadMicroPython().then((py) => {
+        py.runPython("1");
+        py.runPython("print('hello')");
+        py.runPython("import sys; print(f'hello from {sys.platform}')");
+        py.runPython("import collections; print(collections.OrderedDict)");
+    });
+});
diff --git a/tests/ports/webassembly/basic.js.exp b/tests/ports/webassembly/basic.js.exp
new file mode 100644
index 000000000000..6459b2492cf8
--- /dev/null
+++ b/tests/ports/webassembly/basic.js.exp
@@ -0,0 +1,3 @@
+hello
+hello from webassembly
+
diff --git a/tests/ports/webassembly/filesystem.mjs b/tests/ports/webassembly/filesystem.mjs
new file mode 100644
index 000000000000..e9e2920a1610
--- /dev/null
+++ b/tests/ports/webassembly/filesystem.mjs
@@ -0,0 +1,9 @@
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.FS.mkdir("/lib/");
+mp.FS.writeFile("/lib/testmod.py", "x = 1; print(__name__, x)");
+mp.runPython("import testmod");
+
+mp.runPython("import sys; sys.modules.clear()");
+const testmod = mp.pyimport("testmod");
+console.log("testmod:", testmod, testmod.x);
diff --git a/tests/ports/webassembly/filesystem.mjs.exp b/tests/ports/webassembly/filesystem.mjs.exp
new file mode 100644
index 000000000000..25a48b10832c
--- /dev/null
+++ b/tests/ports/webassembly/filesystem.mjs.exp
@@ -0,0 +1,3 @@
+testmod 1
+testmod 1
+testmod: PyProxy { _ref: 3 } 1
diff --git a/tests/ports/webassembly/float.mjs b/tests/ports/webassembly/float.mjs
new file mode 100644
index 000000000000..53bb8b1c4d0a
--- /dev/null
+++ b/tests/ports/webassembly/float.mjs
@@ -0,0 +1,43 @@
+// Test passing floats between JavaScript and Python.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+globalThis.a = 1 / 2;
+globalThis.b = Infinity;
+globalThis.c = NaN;
+
+mp.runPython(`
+import js
+
+# Test retrieving floats from JS.
+print(js.a)
+print(js.b)
+print(js.c)
+
+# Test calling JS which returns a float.
+r = js.Math.random()
+print(type(r), 0 < r < 1)
+
+x = 1 / 2
+y = float("inf")
+z = float("nan")
+
+# Test passing floats to a JS function.
+js.console.log(x)
+js.console.log(x, y)
+js.console.log(x, y, z)
+`);
+
+// Test retrieving floats from Python.
+console.log(mp.globals.get("x"));
+console.log(mp.globals.get("y"));
+console.log(mp.globals.get("z"));
+
+// Test passing floats to a Python function.
+const mp_print = mp.pyimport("builtins").print;
+mp_print(globalThis.a);
+mp_print(globalThis.a, globalThis.b);
+mp_print(globalThis.a, globalThis.b, globalThis.c);
+
+// Test calling Python which returns a float.
+console.log(mp.pyimport("math").sqrt(0.16));
diff --git a/tests/ports/webassembly/float.mjs.exp b/tests/ports/webassembly/float.mjs.exp
new file mode 100644
index 000000000000..57eff74acd8a
--- /dev/null
+++ b/tests/ports/webassembly/float.mjs.exp
@@ -0,0 +1,14 @@
+0.5
+inf
+nan
+ True
+0.5
+0.5 Infinity
+0.5 Infinity NaN
+0.5
+Infinity
+NaN
+0.5
+0.5 inf
+0.5 inf nan
+0.4
diff --git a/tests/ports/webassembly/fun_call.mjs b/tests/ports/webassembly/fun_call.mjs
new file mode 100644
index 000000000000..295745d2e2e3
--- /dev/null
+++ b/tests/ports/webassembly/fun_call.mjs
@@ -0,0 +1,17 @@
+// Test calling JavaScript functions from Python.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+globalThis.f = (a, b, c, d, e) => {
+    console.log(a, b, c, d, e);
+};
+mp.runPython(`
+import js
+js.f()
+js.f(1)
+js.f(1, 2)
+js.f(1, 2, 3)
+js.f(1, 2, 3, 4)
+js.f(1, 2, 3, 4, 5)
+js.f(1, 2, 3, 4, 5, 6)
+`);
diff --git a/tests/ports/webassembly/fun_call.mjs.exp b/tests/ports/webassembly/fun_call.mjs.exp
new file mode 100644
index 000000000000..e9ed5f6ddf9d
--- /dev/null
+++ b/tests/ports/webassembly/fun_call.mjs.exp
@@ -0,0 +1,7 @@
+undefined undefined undefined undefined undefined
+1 undefined undefined undefined undefined
+1 2 undefined undefined undefined
+1 2 3 undefined undefined
+1 2 3 4 undefined
+1 2 3 4 5
+1 2 3 4 5
diff --git a/tests/ports/webassembly/globals.mjs b/tests/ports/webassembly/globals.mjs
new file mode 100644
index 000000000000..3d5ecb416787
--- /dev/null
+++ b/tests/ports/webassembly/globals.mjs
@@ -0,0 +1,13 @@
+// Test accessing Python globals dict via mp.globals.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython("x = 1");
+console.log(mp.globals.get("x"));
+
+mp.globals.set("y", 2);
+mp.runPython("print(y)");
+
+mp.runPython("print('y' in globals())");
+mp.globals.delete("y");
+mp.runPython("print('y' in globals())");
diff --git a/tests/ports/webassembly/globals.mjs.exp b/tests/ports/webassembly/globals.mjs.exp
new file mode 100644
index 000000000000..a118c13fecba
--- /dev/null
+++ b/tests/ports/webassembly/globals.mjs.exp
@@ -0,0 +1,4 @@
+1
+2
+True
+False
diff --git a/tests/ports/webassembly/heap_expand.mjs b/tests/ports/webassembly/heap_expand.mjs
new file mode 100644
index 000000000000..2cf7c07b0b82
--- /dev/null
+++ b/tests/ports/webassembly/heap_expand.mjs
@@ -0,0 +1,15 @@
+// Test expanding the MicroPython GC heap.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+import gc
+bs = []
+for i in range(24):
+    b = bytearray(1 << i)
+    bs.append(b)
+    gc.collect()
+    print(gc.mem_free())
+for b in bs:
+    print(len(b))
+`);
diff --git a/tests/ports/webassembly/heap_expand.mjs.exp b/tests/ports/webassembly/heap_expand.mjs.exp
new file mode 100644
index 000000000000..ee14908409d8
--- /dev/null
+++ b/tests/ports/webassembly/heap_expand.mjs.exp
@@ -0,0 +1,48 @@
+135241360
+135241328
+135241296
+135241264
+135241216
+135241168
+135241088
+135240944
+135240640
+135240112
+135239072
+135237008
+135232896
+135224688
+135208288
+135175504
+135109888
+134978800
+134716640
+135216848
+136217216
+138218032
+142219616
+150222864
+1
+2
+4
+8
+16
+32
+64
+128
+256
+512
+1024
+2048
+4096
+8192
+16384
+32768
+65536
+131072
+262144
+524288
+1048576
+2097152
+4194304
+8388608
diff --git a/tests/ports/webassembly/jsffi_create_proxy.mjs b/tests/ports/webassembly/jsffi_create_proxy.mjs
new file mode 100644
index 000000000000..5f04bf44d7e2
--- /dev/null
+++ b/tests/ports/webassembly/jsffi_create_proxy.mjs
@@ -0,0 +1,15 @@
+// Test jsffi.create_proxy().
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+import jsffi
+x = jsffi.create_proxy(1)
+print(x)
+y = jsffi.create_proxy([2])
+print(y)
+`);
+console.log(mp.globals.get("x"));
+console.log(mp.PyProxy.toJs(mp.globals.get("x")));
+console.log(mp.globals.get("y"));
+console.log(mp.PyProxy.toJs(mp.globals.get("y")));
diff --git a/tests/ports/webassembly/jsffi_create_proxy.mjs.exp b/tests/ports/webassembly/jsffi_create_proxy.mjs.exp
new file mode 100644
index 000000000000..a3b38a78b113
--- /dev/null
+++ b/tests/ports/webassembly/jsffi_create_proxy.mjs.exp
@@ -0,0 +1,6 @@
+1
+
+1
+1
+PyProxy { _ref: 3 }
+[ 2 ]
diff --git a/tests/ports/webassembly/jsffi_to_js.mjs b/tests/ports/webassembly/jsffi_to_js.mjs
new file mode 100644
index 000000000000..714af6b62993
--- /dev/null
+++ b/tests/ports/webassembly/jsffi_to_js.mjs
@@ -0,0 +1,28 @@
+// Test jsffi.to_js().
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+import jsffi
+x = jsffi.to_js(1)
+print(x)
+y = jsffi.to_js([2])
+print(y)
+z = jsffi.to_js({"three":3})
+print(z)
+`);
+
+const x = mp.globals.get("x");
+const y = mp.globals.get("y");
+const z = mp.globals.get("z");
+
+console.log(Array.isArray(x));
+console.log(x);
+
+console.log(Array.isArray(y));
+console.log(y);
+console.log(Reflect.ownKeys(y));
+
+console.log(Array.isArray(z));
+console.log(z);
+console.log(Reflect.ownKeys(z));
diff --git a/tests/ports/webassembly/jsffi_to_js.mjs.exp b/tests/ports/webassembly/jsffi_to_js.mjs.exp
new file mode 100644
index 000000000000..399dd0aa8c9c
--- /dev/null
+++ b/tests/ports/webassembly/jsffi_to_js.mjs.exp
@@ -0,0 +1,11 @@
+1
+
+
+false
+1
+true
+[ 2 ]
+[ '0', 'length' ]
+false
+{ three: 3 }
+[ 'three' ]
diff --git a/tests/ports/webassembly/override_new.mjs b/tests/ports/webassembly/override_new.mjs
new file mode 100644
index 000000000000..f5d64d7d1137
--- /dev/null
+++ b/tests/ports/webassembly/override_new.mjs
@@ -0,0 +1,33 @@
+// Test overriding .new() on a JavaScript class.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+globalThis.MyClass1 = class {
+    new() {
+        console.log("MyClass1 new");
+        return 1;
+    }
+};
+
+globalThis.MyClass2 = class {
+    static new() {
+        console.log("MyClass2 static new");
+        return 2;
+    }
+    new() {
+        console.log("MyClass2 new");
+        return 3;
+    }
+};
+
+globalThis.myClass2Instance = new globalThis.MyClass2();
+
+mp.runPython(`
+    import js
+
+    print(type(js.MyClass1.new()))
+    print(js.MyClass1.new().new())
+
+    print(js.MyClass2.new())
+    print(js.myClass2Instance.new())
+`);
diff --git a/tests/ports/webassembly/override_new.mjs.exp b/tests/ports/webassembly/override_new.mjs.exp
new file mode 100644
index 000000000000..2efb669714ed
--- /dev/null
+++ b/tests/ports/webassembly/override_new.mjs.exp
@@ -0,0 +1,7 @@
+
+MyClass1 new
+1
+MyClass2 static new
+2
+MyClass2 new
+3
diff --git a/tests/ports/webassembly/promise_with_resolvers.mjs b/tests/ports/webassembly/promise_with_resolvers.mjs
new file mode 100644
index 000000000000..a2c6d509a609
--- /dev/null
+++ b/tests/ports/webassembly/promise_with_resolvers.mjs
@@ -0,0 +1,23 @@
+// Test polyfill of a method on a built-in.
+
+// Implement Promise.withResolvers, and make sure it has a unique name so
+// the test below is guaranteed to use this version.
+Promise.withResolversCustom = function withResolversCustom() {
+    let a;
+    let b;
+    const c = new this((resolve, reject) => {
+        a = resolve;
+        b = reject;
+    });
+    return { resolve: a, reject: b, promise: c };
+};
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+    from js import Promise
+
+    deferred = Promise.withResolversCustom()
+    deferred.promise.then(print)
+    deferred.resolve('OK')
+`);
diff --git a/tests/ports/webassembly/promise_with_resolvers.mjs.exp b/tests/ports/webassembly/promise_with_resolvers.mjs.exp
new file mode 100644
index 000000000000..d86bac9de59a
--- /dev/null
+++ b/tests/ports/webassembly/promise_with_resolvers.mjs.exp
@@ -0,0 +1 @@
+OK
diff --git a/tests/ports/webassembly/py_proxy_delete.mjs b/tests/ports/webassembly/py_proxy_delete.mjs
new file mode 100644
index 000000000000..2afea0b9f222
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_delete.mjs
@@ -0,0 +1,30 @@
+// Test `delete .` on the JavaScript side, which tests PyProxy.deleteProperty.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+class A:
+    pass
+x = A()
+x.foo = 1
+y = []
+`);
+
+const x = mp.globals.get("x");
+const y = mp.globals.get("y");
+
+// Should pass.
+// biome-ignore lint/performance/noDelete: test delete statement
+delete x.foo;
+
+mp.runPython(`
+print(hasattr(x, "foo"))
+`);
+
+// Should fail, can't delete attributes on MicroPython lists.
+try {
+    // biome-ignore lint/performance/noDelete: test delete statement
+    delete y.sort;
+} catch (error) {
+    console.log(error.message);
+}
diff --git a/tests/ports/webassembly/py_proxy_delete.mjs.exp b/tests/ports/webassembly/py_proxy_delete.mjs.exp
new file mode 100644
index 000000000000..8eb9ad150141
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_delete.mjs.exp
@@ -0,0 +1,2 @@
+False
+'deleteProperty' on proxy: trap returned falsish for property 'sort'
diff --git a/tests/ports/webassembly/py_proxy_dict.mjs b/tests/ports/webassembly/py_proxy_dict.mjs
new file mode 100644
index 000000000000..201f179ef377
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_dict.mjs
@@ -0,0 +1,34 @@
+// Test passing a Python dict into JavaScript, it should act like a JS object.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+x = {"a": 1, "b": 2}
+`);
+
+const x = mp.globals.get("x");
+
+// Test has, get, keys/iteration.
+console.log("a" in x, "b" in x, "c" in x);
+console.log(x.a, x.b);
+for (const k in x) {
+    console.log(k, x[k]);
+}
+console.log(Object.keys(x));
+console.log(Reflect.ownKeys(x));
+
+// Test set.
+x.c = 3;
+console.log(Object.keys(x));
+
+// Test delete.
+// biome-ignore lint/performance/noDelete: test delete statement
+delete x.b;
+console.log(Object.keys(x));
+
+// Make sure changes on the JavaScript side are reflected in Python.
+mp.runPython(`
+print(x["a"])
+print("b" in x)
+print(x["c"])
+`);
diff --git a/tests/ports/webassembly/py_proxy_dict.mjs.exp b/tests/ports/webassembly/py_proxy_dict.mjs.exp
new file mode 100644
index 000000000000..f0e15034bd71
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_dict.mjs.exp
@@ -0,0 +1,11 @@
+true true false
+1 2
+a 1
+b 2
+[ 'a', 'b' ]
+[ 'a', 'b' ]
+[ 'a', 'c', 'b' ]
+[ 'a', 'c' ]
+1
+False
+3
diff --git a/tests/ports/webassembly/py_proxy_has.mjs b/tests/ports/webassembly/py_proxy_has.mjs
new file mode 100644
index 000000000000..8881776fdbe5
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_has.mjs
@@ -0,0 +1,11 @@
+// Test ` in ` on the JavaScript side, which tests PyProxy.has.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+x = []
+`);
+
+const x = mp.globals.get("x");
+console.log("no_exist" in x);
+console.log("sort" in x);
diff --git a/tests/ports/webassembly/py_proxy_has.mjs.exp b/tests/ports/webassembly/py_proxy_has.mjs.exp
new file mode 100644
index 000000000000..1d474d525571
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_has.mjs.exp
@@ -0,0 +1,2 @@
+false
+true
diff --git a/tests/ports/webassembly/py_proxy_own_keys.mjs b/tests/ports/webassembly/py_proxy_own_keys.mjs
new file mode 100644
index 000000000000..bbf5b4f01c05
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_own_keys.mjs
@@ -0,0 +1,11 @@
+// Test `Reflect.ownKeys()` on the JavaScript side, which tests PyProxy.ownKeys.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+x = []
+y = {"a": 1}
+`);
+
+console.log(Reflect.ownKeys(mp.globals.get("x")));
+console.log(Reflect.ownKeys(mp.globals.get("y")));
diff --git a/tests/ports/webassembly/py_proxy_own_keys.mjs.exp b/tests/ports/webassembly/py_proxy_own_keys.mjs.exp
new file mode 100644
index 000000000000..313d06d4d75d
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_own_keys.mjs.exp
@@ -0,0 +1,9 @@
+[
+  'append', 'clear',
+  'copy',   'count',
+  'extend', 'index',
+  'insert', 'pop',
+  'remove', 'reverse',
+  'sort'
+]
+[ 'a' ]
diff --git a/tests/ports/webassembly/py_proxy_set.mjs b/tests/ports/webassembly/py_proxy_set.mjs
new file mode 100644
index 000000000000..30360a847d80
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_set.mjs
@@ -0,0 +1,27 @@
+// Test `. = ` on the JavaScript side, which tests PyProxy.set.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+class A:
+    pass
+x = A()
+y = []
+`);
+
+const x = mp.globals.get("x");
+const y = mp.globals.get("y");
+
+// Should pass.
+x.foo = 1;
+
+mp.runPython(`
+print(x.foo)
+`);
+
+// Should fail, can't set attributes on MicroPython lists.
+try {
+    y.bar = 1;
+} catch (error) {
+    console.log(error.message);
+}
diff --git a/tests/ports/webassembly/py_proxy_set.mjs.exp b/tests/ports/webassembly/py_proxy_set.mjs.exp
new file mode 100644
index 000000000000..e1d995156341
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_set.mjs.exp
@@ -0,0 +1,2 @@
+1
+'set' on proxy: trap returned falsish for property 'bar'
diff --git a/tests/ports/webassembly/py_proxy_to_js.mjs b/tests/ports/webassembly/py_proxy_to_js.mjs
new file mode 100644
index 000000000000..f9fce606f1fd
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_to_js.mjs
@@ -0,0 +1,20 @@
+// Test PyProxy.toJs().
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+mp.runPython(`
+a = 1
+b = (1, 2, 3)
+c = [None, True, 1.2]
+d = {"one": 1, "tuple": b, "list": c}
+`);
+
+const py_a = mp.globals.get("a");
+const py_b = mp.globals.get("b");
+const py_c = mp.globals.get("c");
+const py_d = mp.globals.get("d");
+
+console.log(py_a instanceof mp.PyProxy, mp.PyProxy.toJs(py_a));
+console.log(py_b instanceof mp.PyProxy, mp.PyProxy.toJs(py_b));
+console.log(py_c instanceof mp.PyProxy, mp.PyProxy.toJs(py_c));
+console.log(py_d instanceof mp.PyProxy, mp.PyProxy.toJs(py_d));
diff --git a/tests/ports/webassembly/py_proxy_to_js.mjs.exp b/tests/ports/webassembly/py_proxy_to_js.mjs.exp
new file mode 100644
index 000000000000..279df7bdfbb8
--- /dev/null
+++ b/tests/ports/webassembly/py_proxy_to_js.mjs.exp
@@ -0,0 +1,4 @@
+false 1
+true [ 1, 2, 3 ]
+true [ null, true, 1.2 ]
+true { tuple: [ 1, 2, 3 ], one: 1, list: [ null, true, 1.2 ] }
diff --git a/tests/ports/webassembly/register_js_module.js b/tests/ports/webassembly/register_js_module.js
new file mode 100644
index 000000000000..b512f2c0dd46
--- /dev/null
+++ b/tests/ports/webassembly/register_js_module.js
@@ -0,0 +1,6 @@
+import(process.argv[2]).then((mp) => {
+    mp.loadMicroPython().then((py) => {
+        py.registerJsModule("js_module", { y: 2 });
+        py.runPython("import js_module; print(js_module); print(js_module.y)");
+    });
+});
diff --git a/tests/ports/webassembly/register_js_module.js.exp b/tests/ports/webassembly/register_js_module.js.exp
new file mode 100644
index 000000000000..bb45f4ce0028
--- /dev/null
+++ b/tests/ports/webassembly/register_js_module.js.exp
@@ -0,0 +1,2 @@
+
+2
diff --git a/tests/ports/webassembly/run_python_async.mjs b/tests/ports/webassembly/run_python_async.mjs
new file mode 100644
index 000000000000..44f2a90ab175
--- /dev/null
+++ b/tests/ports/webassembly/run_python_async.mjs
@@ -0,0 +1,115 @@
+// Test runPythonAsync() and top-level await in Python.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+/**********************************************************/
+// Using only promise objects, no await's.
+
+console.log("= TEST 1 ==========");
+
+globalThis.p = new Promise((resolve, reject) => {
+    resolve(123);
+});
+
+console.log(1);
+
+mp.runPython(`
+import js
+print(js.p)
+print("py 1")
+print(js.p.then(lambda x: print("resolved", x)))
+print("py 2")
+`);
+
+console.log(2);
+
+// Let the promise resolve.
+await globalThis.p;
+
+console.log(3);
+
+/**********************************************************/
+// Using setTimeout to resolve the promise.
+
+console.log("= TEST 2 ==========");
+
+globalThis.p = new Promise((resolve, reject) => {
+    setTimeout(() => {
+        resolve(123);
+        console.log("setTimeout resolved");
+    }, 100);
+});
+
+console.log(1);
+
+mp.runPython(`
+import js
+print(js.p)
+print("py 1")
+print(js.p.then(lambda x: print("resolved", x)))
+print("py 2")
+`);
+
+console.log(2);
+
+// Let the promise resolve.
+await globalThis.p;
+
+console.log(3);
+
+/**********************************************************/
+// Using setTimeout and await within Python.
+
+console.log("= TEST 3 ==========");
+
+globalThis.p = new Promise((resolve, reject) => {
+    setTimeout(() => {
+        resolve(123);
+        console.log("setTimeout resolved");
+    }, 100);
+});
+
+console.log(1);
+
+const ret3 = await mp.runPythonAsync(`
+import js
+print("py 1")
+print("resolved value:", await js.p)
+print("py 2")
+`);
+
+console.log(2, ret3);
+
+/**********************************************************/
+// Multiple setTimeout's and await's within Python.
+
+console.log("= TEST 4 ==========");
+
+globalThis.p1 = new Promise((resolve, reject) => {
+    setTimeout(() => {
+        resolve(123);
+        console.log("setTimeout A resolved");
+    }, 100);
+});
+
+globalThis.p2 = new Promise((resolve, reject) => {
+    setTimeout(() => {
+        resolve(456);
+        console.log("setTimeout B resolved");
+    }, 200);
+});
+
+console.log(1);
+
+const ret4 = await mp.runPythonAsync(`
+import js
+print("py 1")
+print("resolved value:", await js.p1)
+print("py 2")
+print("resolved value:", await js.p1)
+print("py 3")
+print("resolved value:", await js.p2)
+print("py 4")
+`);
+
+console.log(2, ret4);
diff --git a/tests/ports/webassembly/run_python_async.mjs.exp b/tests/ports/webassembly/run_python_async.mjs.exp
new file mode 100644
index 000000000000..f441bc5cf1db
--- /dev/null
+++ b/tests/ports/webassembly/run_python_async.mjs.exp
@@ -0,0 +1,38 @@
+= TEST 1 ==========
+1
+
+py 1
+
+py 2
+2
+resolved 123
+3
+= TEST 2 ==========
+1
+
+py 1
+
+py 2
+2
+setTimeout resolved
+resolved 123
+3
+= TEST 3 ==========
+1
+py 1
+setTimeout resolved
+resolved value: 123
+py 2
+2 null
+= TEST 4 ==========
+1
+py 1
+setTimeout A resolved
+resolved value: 123
+py 2
+resolved value: 123
+py 3
+setTimeout B resolved
+resolved value: 456
+py 4
+2 null
diff --git a/tests/ports/webassembly/run_python_async2.mjs b/tests/ports/webassembly/run_python_async2.mjs
new file mode 100644
index 000000000000..87067e6e8c40
--- /dev/null
+++ b/tests/ports/webassembly/run_python_async2.mjs
@@ -0,0 +1,42 @@
+// Test runPythonAsync() and top-level await in Python, with multi-level awaits.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+// simulate a 2-step resolution of the string "OK".
+await mp.runPythonAsync(`
+import js
+
+def _timeout(resolve, _):
+    js.setTimeout(resolve, 100)
+
+def _fetch():
+    return js.Promise.new(_timeout)
+
+async def _text(promise):
+    if not promise._response:
+        print("_text await start")
+        await promise
+        print("_text awaited end")
+    ret = await promise._response.text()
+    return ret
+
+class _Response:
+    async def text(self):
+        print('_Response.text start')
+        await js.Promise.new(_timeout)
+        print('_Response.text end')
+        return "OK"
+
+def _response(promise):
+    promise._response = _Response()
+    return promise._response
+
+def fetch(url):
+    promise = _fetch().then(lambda *_: _response(promise))
+    promise._response = None
+    promise.text = lambda: _text(promise)
+    return promise
+
+print(await fetch("config.json").text())
+print(await (await fetch("config.json")).text())
+`);
diff --git a/tests/ports/webassembly/run_python_async2.mjs.exp b/tests/ports/webassembly/run_python_async2.mjs.exp
new file mode 100644
index 000000000000..60d68c5d3b55
--- /dev/null
+++ b/tests/ports/webassembly/run_python_async2.mjs.exp
@@ -0,0 +1,8 @@
+_text await start
+_text awaited end
+_Response.text start
+_Response.text end
+OK
+_Response.text start
+_Response.text end
+OK
diff --git a/tests/ports/webassembly/this_behaviour.mjs b/tests/ports/webassembly/this_behaviour.mjs
new file mode 100644
index 000000000000..6411b6ce6348
--- /dev/null
+++ b/tests/ports/webassembly/this_behaviour.mjs
@@ -0,0 +1,24 @@
+// Test "this" behaviour.
+
+const mp = await (await import(process.argv[2])).loadMicroPython();
+
+// "this" should be undefined.
+globalThis.func0 = function () {
+    console.log("func0", this);
+};
+mp.runPython("import js; js.func0()");
+
+globalThis.func1 = function (a) {
+    console.log("func1", a, this);
+};
+mp.runPython("import js; js.func1(123)");
+
+globalThis.func2 = function (a, b) {
+    console.log("func2", a, b, this);
+};
+mp.runPython("import js; js.func2(123, 456)");
+
+globalThis.func3 = function (a, b, c) {
+    console.log("func3", a, b, c, this);
+};
+mp.runPython("import js; js.func3(123, 456, 789)");
diff --git a/tests/ports/webassembly/this_behaviour.mjs.exp b/tests/ports/webassembly/this_behaviour.mjs.exp
new file mode 100644
index 000000000000..4762026ab202
--- /dev/null
+++ b/tests/ports/webassembly/this_behaviour.mjs.exp
@@ -0,0 +1,4 @@
+func0 undefined
+func1 123 undefined
+func2 123 456 undefined
+func3 123 456 789 undefined
diff --git a/tests/ports/webassembly/various.js b/tests/ports/webassembly/various.js
new file mode 100644
index 000000000000..e2fa9362c4f3
--- /dev/null
+++ b/tests/ports/webassembly/various.js
@@ -0,0 +1,38 @@
+import(process.argv[2]).then((mp) => {
+    mp.loadMicroPython().then((py) => {
+        globalThis.jsadd = (x, y) => {
+            return x + y;
+        };
+        py.runPython("import js; print(js); print(js.jsadd(4, 9))");
+
+        py.runPython(
+            "def set_timeout_callback():\n print('set_timeout_callback')",
+        );
+        py.runPython("import js; js.setTimeout(set_timeout_callback, 100)");
+
+        py.runPython("obj = js.Object(a=1)");
+        console.log("main", py.pyimport("__main__").obj);
+
+        console.log("=======");
+        py.runPython(`
+            from js import Array, Promise, Reflect
+
+            def callback(resolve, reject):
+                resolve('OK1')
+
+            p = Reflect.construct(Promise, Array(callback))
+            p.then(print)
+        `);
+
+        console.log("=======");
+        py.runPython(`
+            from js import Promise
+
+            def callback(resolve, reject):
+                resolve('OK2')
+
+            p = Promise.new(callback)
+            p.then(print)
+        `);
+    });
+});
diff --git a/tests/ports/webassembly/various.js.exp b/tests/ports/webassembly/various.js.exp
new file mode 100644
index 000000000000..502ab2cccf00
--- /dev/null
+++ b/tests/ports/webassembly/various.js.exp
@@ -0,0 +1,8 @@
+
+13
+main { a: 1 }
+=======
+=======
+OK1
+OK2
+set_timeout_callback
diff --git a/tests/run-tests.py b/tests/run-tests.py
index 70279d379df6..4f55cdd39842 100755
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -308,7 +308,9 @@ def send_get(what):
 
     else:
         # run via pyboard interface
-        had_crash, output_mupy = run_script_on_remote_target(pyb, args, test_file, is_special)
+        had_crash, output_mupy = pyb.run_script_on_remote_target(
+            args, test_file_abspath, is_special
+        )
 
     # canonical form for all ports/platforms is to use \n for end-of-line
     output_mupy = output_mupy.replace(b"\r\n", b"\n")
@@ -393,6 +395,51 @@ def value(self):
         return self._value
 
 
+class PyboardNodeRunner:
+    def __init__(self):
+        mjs = os.getenv("MICROPY_MICROPYTHON_MJS")
+        if mjs is None:
+            mjs = base_path("../ports/webassembly/build-standard/micropython.mjs")
+        else:
+            mjs = os.path.abspath(mjs)
+        self.micropython_mjs = mjs
+
+    def close(self):
+        pass
+
+    def run_script_on_remote_target(self, args, test_file, is_special):
+        cwd = os.path.dirname(test_file)
+
+        # Create system command list.
+        cmdlist = ["node"]
+        if test_file.endswith(".py"):
+            # Run a Python script indirectly via "node micropython.mjs ".
+            cmdlist.append(self.micropython_mjs)
+            if args.heapsize is not None:
+                cmdlist.extend(["-X", "heapsize=" + args.heapsize])
+            cmdlist.append(test_file)
+        else:
+            # Run a js/mjs script directly with Node, passing in the path to micropython.mjs.
+            cmdlist.append(test_file)
+            cmdlist.append(self.micropython_mjs)
+
+        # Run the script.
+        try:
+            had_crash = False
+            output_mupy = subprocess.check_output(
+                cmdlist, stderr=subprocess.STDOUT, timeout=TEST_TIMEOUT, cwd=cwd
+            )
+        except subprocess.CalledProcessError as er:
+            had_crash = True
+            output_mupy = er.output + b"CRASH"
+        except subprocess.TimeoutExpired as er:
+            had_crash = True
+            output_mupy = (er.output or b"") + b"TIMEOUT"
+
+        # Return the results.
+        return had_crash, output_mupy
+
+
 def run_tests(pyb, tests, args, result_dir, num_threads=1):
     test_count = ThreadSafeCounter()
     testcase_count = ThreadSafeCounter()
@@ -631,6 +678,20 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
             )  # RA fsp rtc function doesn't support nano sec info
         elif args.target == "qemu-arm":
             skip_tests.add("misc/print_exception.py")  # requires sys stdfiles
+        elif args.target == "webassembly":
+            skip_tests.add("basics/string_format_modulo.py")  # can't print nulls to stdout
+            skip_tests.add("basics/string_strip.py")  # can't print nulls to stdout
+            skip_tests.add("extmod/binascii_a2b_base64.py")
+            skip_tests.add("extmod/re_stack_overflow.py")
+            skip_tests.add("extmod/time_res.py")
+            skip_tests.add("extmod/vfs_posix.py")
+            skip_tests.add("extmod/vfs_posix_enoent.py")
+            skip_tests.add("extmod/vfs_posix_paths.py")
+            skip_tests.add("extmod/vfs_userfs.py")
+            skip_tests.add("micropython/emg_exc.py")
+            skip_tests.add("micropython/extreme_exc.py")
+            skip_tests.add("micropython/heapalloc_exc_compressed_emg_exc.py")
+            skip_tests.add("micropython/import_mpy_invalid.py")
 
     # Some tests are known to fail on 64-bit machines
     if pyb is None and platform.architecture()[0] == "64bit":
@@ -977,6 +1038,7 @@ def main():
     LOCAL_TARGETS = (
         "unix",
         "qemu-arm",
+        "webassembly",
     )
     EXTERNAL_TARGETS = (
         "pyboard",
@@ -997,6 +1059,8 @@ def main():
                 args.mpy_cross_flags = "-march=host"
             elif args.target == "qemu-arm":
                 args.mpy_cross_flags = "-march=armv7m"
+        if args.target == "webassembly":
+            pyb = PyboardNodeRunner()
     elif args.target in EXTERNAL_TARGETS:
         global pyboard
         sys.path.append(base_path("../tools"))
@@ -1015,6 +1079,7 @@ def main():
                 args.mpy_cross_flags = "-march=armv7m"
 
         pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password)
+        pyboard.Pyboard.run_script_on_remote_target = run_script_on_remote_target
         pyb.enter_raw_repl()
     else:
         raise ValueError("target must be one of %s" % ", ".join(LOCAL_TARGETS + EXTERNAL_TARGETS))
@@ -1032,6 +1097,10 @@ def main():
         else:
             tests = []
     elif len(args.files) == 0:
+        test_extensions = ("*.py",)
+        if args.target == "webassembly":
+            test_extensions += ("*.js", "*.mjs")
+
         if args.test_dirs is None:
             test_dirs = (
                 "basics",
@@ -1072,12 +1141,16 @@ def main():
                     "inlineasm",
                     "ports/qemu-arm",
                 )
+            elif args.target == "webassembly":
+                test_dirs += ("float", "ports/webassembly")
         else:
             # run tests from these directories
             test_dirs = args.test_dirs
         tests = sorted(
             test_file
-            for test_files in (glob("{}/*.py".format(dir)) for dir in test_dirs)
+            for test_files in (
+                glob(os.path.join(dir, ext)) for dir in test_dirs for ext in test_extensions
+            )
             for test_file in test_files
         )
     else:
diff --git a/tools/ci.sh b/tools/ci.sh
index 3cbc51cfad62..ad5e79c4ad07 100755
--- a/tools/ci.sh
+++ b/tools/ci.sh
@@ -178,18 +178,19 @@ function ci_esp8266_build {
 # ports/webassembly
 
 function ci_webassembly_setup {
+    npm install terser
     git clone https://github.com/emscripten-core/emsdk.git
     (cd emsdk && ./emsdk install latest && ./emsdk activate latest)
 }
 
 function ci_webassembly_build {
     source emsdk/emsdk_env.sh
-    make ${MAKEOPTS} -C ports/webassembly
+    make ${MAKEOPTS} -C ports/webassembly VARIANT=pyscript submodules
+    make ${MAKEOPTS} -C ports/webassembly VARIANT=pyscript
 }
 
 function ci_webassembly_run_tests {
-    # This port is very slow at running, so only run a few of the tests.
-    (cd tests && MICROPY_MICROPYTHON=../ports/webassembly/node_run.sh ./run-tests.py -j1 basics/builtin_*.py)
+    make -C ports/webassembly VARIANT=pyscript test_min
 }
 
 ########################################################################################
@@ -366,6 +367,12 @@ function ci_stm32_nucleo_build {
     diff $BUILD_WB55/firmware.unpack.dfu $BUILD_WB55/firmware.unpack_no_sk.dfu
 }
 
+function ci_stm32_misc_build {
+    make ${MAKEOPTS} -C mpy-cross
+    make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA submodules
+    make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA
+}
+
 ########################################################################################
 # ports/unix
 
diff --git a/tools/codeformat.py b/tools/codeformat.py
index bd37aec467f1..c65174ddc127 100755
--- a/tools/codeformat.py
+++ b/tools/codeformat.py
@@ -59,11 +59,9 @@
     "ports/nrf/drivers/*.[ch]",
     "ports/nrf/modules/ble/*.[ch]",
     "ports/nrf/modules/board/*.[ch]",
-    "ports/nrf/modules/machine/*.[ch]",
     "ports/nrf/modules/music/*.[ch]",
     "ports/nrf/modules/ubluepy/*.[ch]",
     "ports/nrf/modules/os/*.[ch]",
-    "ports/nrf/modules/time/*.[ch]",
     # STM32 USB dev/host code is mostly 3rd party.
     "ports/stm32/usbdev/**/*.[ch]",
     "ports/stm32/usbhost/**/*.[ch]",
diff --git a/tools/manifestfile.py b/tools/manifestfile.py
index c1fc836585fb..beaa36d0f5fc 100644
--- a/tools/manifestfile.py
+++ b/tools/manifestfile.py
@@ -291,7 +291,7 @@ def _add_file(self, full_path, target_path, kind=KIND_AUTO, opt=None):
     def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=False):
         base_path = self._resolve_path(base_path)
 
-        if files:
+        if files is not None:
             # Use explicit list of files (relative to package_path).
             for file in files:
                 if package_path:
diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py
index 35c06a30282e..86c4ba757935 100755
--- a/tools/mpy-tool.py
+++ b/tools/mpy-tool.py
@@ -88,7 +88,7 @@ def __str__(self):
 
 class Config:
     MPY_VERSION = 6
-    MPY_SUB_VERSION = 2
+    MPY_SUB_VERSION = 3
     MICROPY_LONGINT_IMPL_NONE = 0
     MICROPY_LONGINT_IMPL_LONGLONG = 1
     MICROPY_LONGINT_IMPL_MPZ = 2
@@ -1730,10 +1730,13 @@ def copy_section(file, offset, offset2):
         bytecode.append(0b00000010)  # prelude size (n_info=1, n_cell=0)
         bytecode.extend(b"\x00")  # simple_name: qstr index 0 (will use source filename)
         for idx in range(len(compiled_modules)):
-            bytecode.append(0x32)  # MP_BC_MAKE_FUNCTION
-            bytecode.append(idx)  # index raw code
-            bytecode.extend(b"\x34\x00\x59")  # MP_BC_CALL_FUNCTION, 0 args, MP_BC_POP_TOP
-        bytecode.extend(b"\x51\x63")  # MP_BC_LOAD_NONE, MP_BC_RETURN_VALUE
+            bytecode.append(Opcode.MP_BC_MAKE_FUNCTION)
+            bytecode.extend(mp_encode_uint(idx))  # index of raw code
+            bytecode.append(Opcode.MP_BC_CALL_FUNCTION)
+            bytecode.append(0)  # 0 arguments
+            bytecode.append(Opcode.MP_BC_POP_TOP)
+        bytecode.append(Opcode.MP_BC_LOAD_CONST_NONE)
+        bytecode.append(Opcode.MP_BC_RETURN_VALUE)
 
         merged_mpy.extend(mp_encode_uint(len(bytecode) << 3 | 1 << 2))  # length, has_children
         merged_mpy.extend(bytecode)
diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py
index d7d0b945c5db..2e3da53eb57e 100755
--- a/tools/mpy_ld.py
+++ b/tools/mpy_ld.py
@@ -36,7 +36,7 @@
 
 # MicroPython constants
 MPY_VERSION = 6
-MPY_SUB_VERSION = 2
+MPY_SUB_VERSION = 3
 MP_CODE_BYTECODE = 2
 MP_CODE_NATIVE_VIPER = 4
 MP_NATIVE_ARCH_X86 = 1
@@ -52,6 +52,7 @@
 MP_SCOPE_FLAG_VIPERRODATA = 0x20
 MP_SCOPE_FLAG_VIPERBSS = 0x40
 MP_SMALL_INT_BITS = 31
+MP_FUN_TABLE_MP_TYPE_TYPE_OFFSET = 70
 
 # ELF constants
 R_386_32 = 1
@@ -754,7 +755,7 @@ def link_objects(env, native_qstr_vals_len):
     # Resolve unknown symbols
     mp_fun_table_sec = Section(".external.mp_fun_table", b"", 0)
     fun_table = {
-        key: 67 + idx
+        key: MP_FUN_TABLE_MP_TYPE_TYPE_OFFSET + idx
         for idx, key in enumerate(
             [
                 "mp_type_type",