diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index 84d30b27f6..f5e01dc1f6 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -25,6 +25,7 @@ jobs: ci_func: # names are functions in ci.sh - stm32_pyb_build - stm32_nucleo_build + - stm32_misc_build runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 diff --git a/.gitmodules b/.gitmodules index 75bffdaddd..423d1bbeed 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/lwip-tcpip/lwip.git [submodule "lib/berkeley-db-1.xx"] path = lib/berkeley-db-1.xx - url = https://github.com/pfalcon/berkeley-db-1.xx + url = https://github.com/micropython/berkeley-db-1.xx [submodule "lib/stm32lib"] path = lib/stm32lib url = https://github.com/micropython/stm32lib @@ -59,3 +59,9 @@ [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 diff --git a/LICENSE b/LICENSE index b85f31808a..9142c46045 100644 --- a/LICENSE +++ b/LICENSE @@ -48,12 +48,14 @@ used during the build process and is not part of the compiled source code. /cmsis (BSD-3-clause) /crypto-algorithms (NONE) /libhydrogen (ISC) + /libmetal (BSD-3-clause) /littlefs (BSD-3-clause) /lwip (BSD-3-clause) /mynewt-nimble (Apache-2.0) /nrfx (BSD-3-clause) /nxp_driver (BSD-3-Clause) /oofatfs (BSD-1-clause) + /open-amp (BSD-3-clause) /pico-sdk (BSD-3-clause) /re15 (BSD-3-clause) /stm32lib (BSD-3-clause) diff --git a/docs/library/collections.rst b/docs/library/collections.rst index 6cf2c096ff..6a23e456c6 100644 --- a/docs/library/collections.rst +++ b/docs/library/collections.rst @@ -18,7 +18,9 @@ Classes appends and pops from either side of the deque. New deques are created using the following arguments: - - *iterable* must be the empty tuple, and the new deque is created empty. + - *iterable* is an iterable used to populate the deque when it is + created. It can be an empty tuple or list to create a deque that + is initially empty. - *maxlen* must be specified and the deque will be bounded to this maximum length. Once the deque is full, any new items added will @@ -26,18 +28,37 @@ Classes - The optional *flags* can be 1 to check for overflow when adding items. - As well as supporting `bool` and `len`, deque objects have the following - methods: + Deque objects support `bool`, `len`, iteration and subscript load and store. + They also have the following methods: .. method:: deque.append(x) Add *x* to the right side of the deque. - Raises IndexError if overflow checking is enabled and there is no more room left. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the queue. + + .. method:: deque.appendleft(x) + + Add *x* to the left side of the deque. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the queue. + + .. method:: deque.pop() + + Remove and return an item from the right side of the deque. + Raises ``IndexError`` if no items are present. .. method:: deque.popleft() Remove and return an item from the left side of the deque. - Raises IndexError if no items are present. + Raises ``IndexError`` if no items are present. + + .. method:: deque.extend(iterable) + + Extend the deque by appending all the items from *iterable* to + the right of the deque. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the deque. .. function:: namedtuple(name, fields) diff --git a/docs/library/index.rst b/docs/library/index.rst index eb29b76805..4209a0781a 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -103,6 +103,7 @@ the following libraries. micropython.rst neopixel.rst network.rst + openamp.rst uctypes.rst vfs.rst diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst new file mode 100644 index 0000000000..82897b280d --- /dev/null +++ b/docs/library/machine.USBDevice.rst @@ -0,0 +1,286 @@ +.. currentmodule:: machine +.. _machine.USBDevice: + +class USBDevice -- USB Device driver +==================================== + +.. note:: ``machine.USBDevice`` is currently only supported on the rp2 and samd + ports. + +USBDevice provides a low-level Python API for implementing USB device functions using +Python code. This low-level API assumes familiarity with the USB standard. It's +not recommended to use this API directly, instead install the high-level usbd +module from micropython-lib. + +.. warning:: This functionality is very new and the high-level usbd module is + not yet merged into micropython-lib. It can be found `here on + GitHub `_. + +Terminology +----------- + +- A "Runtime" USB device interface or driver is one which is defined using this + Python API after MicroPython initially starts up. + +- A "Built-in" USB device interface or driver is one that is compiled into the + MicroPython firmware, and is always available. Examples are USB-CDC (serial + port) which is usually enabled by default. Built-in USB-MSC (Mass Storage) is an + option on some ports. + +Lifecycle +--------- + +Managing a runtime USB interface can be tricky, especially if you are communicating +with MicroPython over a built-in USB-CDC serial port that's part of the same USB +device. + +- A MicroPython soft reset will always clear all runtime USB interfaces, which + results in the entire USB device disconnecting from the host. If MicroPython + is also providing a built-in USB-CDC serial port then this will re-appear + after the soft reset. + + This means some functions (like ``mpremote run``) that target the USB-CDC + serial port will immediately fail if a runtime USB interface is active, + because the port goes away when ``mpremote`` triggers a soft reset. The + operation should succeed on the second try, as after the soft reset there is + no more runtime USB interface. + +- To configure a runtime USB device on every boot, it's recommended to place the + configuration code in the ``boot.py`` file on the :ref:`device VFS + `. On each reset this file is executed before the USB subsystem is + initialised (and before ``main.py``), so it allows the board to come up with the runtime + USB device immediately. + +- For development or debugging, it may be convenient to connect a hardware + serial REPL and disable the built-in USB-CDC serial port entirely. Not all ports + support this (currently only ``rp2``). The custom build should be configured + with ``#define MICROPY_HW_USB_CDC (0)`` and ``#define + MICROPY_HW_ENABLE_UART_REPL (1)``. + +Constructors +------------ + +.. class:: USBDevice() + + Construct a USBDevice object. + + .. note:: This object is a singleton, each call to this constructor + returns the same object reference. + +Methods +------- + +.. method:: USBDevice.config(desc_dev, desc_cfg, desc_strs=None, open_itf_cb=None, reset_cb=None, control_xfer_cb=None, xfer_cb=None) + + Configures the ``USBDevice`` singleton object with the USB runtime device + state and callback functions: + + - ``desc_dev`` - A bytes-like object containing + the new USB device descriptor. + + - ``desc_cfg`` - A bytes-like object containing the + new USB configuration descriptor. + + - ``desc_strs`` - Optional object holding strings or bytes objects + containing USB string descriptor values. Can be a list, a dict, or any + object which supports subscript indexing with integer keys (USB string + descriptor index). + + Strings are an optional USB feature, and this parameter can be unset + (default) if no strings are referenced in the device and configuration + descriptors, or if only built-in strings should be used. + + Apart from index 0, all the string values should be plain ASCII. Index 0 + is the special "languages" USB descriptor, represented as a bytes object + with a custom format defined in the USB standard. ``None`` can be + returned at index 0 in order to use a default "English" language + descriptor. + + To fall back to providing a built-in string value for a given index, a + subscript lookup can return ``None``, raise ``KeyError``, or raise + ``IndexError``. + + - ``open_itf_cb`` - This callback is called once for each interface + or Interface Association Descriptor in response to a Set + Configuration request from the USB Host (the final stage before + the USB device is available to the host). + + The callback takes a single argument, which is a memoryview of the + interface or IAD descriptor that the host is accepting (including + all associated descriptors). It is a view into the same + ``desc_cfg`` object that was provided as a separate + argument to this function. The memoryview is only valid until the + callback function returns. + + - ``reset_cb`` - This callback is called when the USB host performs + a bus reset. The callback takes no arguments. Any in-progress + transfers will never complete. The USB host will most likely + proceed to re-enumerate the USB device by calling the descriptor + callbacks and then ``open_itf_cb()``. + + - ``control_xfer_cb`` - This callback is called one or more times + for each USB control transfer (device Endpoint 0). It takes two + arguments. + + The first argument is the control transfer stage. It is one of: + + - ``1`` for SETUP stage. + - ``2`` for DATA stage. + - ``3`` for ACK stage. + + Second argument is a memoryview to read the USB control request + data for this stage. The memoryview is only valid until the + callback function returns. + + The callback should return one of the following values: + + - ``False`` to stall the endpoint and reject the transfer. + - ``True`` to continue the transfer to the next stage. + - A buffer object to provide data for this stage of the transfer. + This should be a writable buffer for an ``OUT`` direction transfer, or a + readable buffer with data for an ``IN`` direction transfer. + + - ``xfer_cb`` - This callback is called whenever a non-control + transfer submitted by calling :func:`USBDevice.submit_xfer` completes. + + The callback has three arguments: + + 1. The Endpoint number for the completed transfer. + 2. Result value: ``True`` if the transfer succeeded, ``False`` + otherwise. + 3. Number of bytes successfully transferred. In the case of a + "short" transfer, The result is ``True`` and ``xferred_bytes`` + will be smaller than the length of the buffer submitted for the + transfer. + + .. note:: If a bus reset occurs (see :func:`USBDevice.reset`), + ``xfer_cb`` is not called for any transfers that have not + already completed. + +.. method:: USBDevice.active(self, [value] /) + + Returns the current active state of this runtime USB device as a + boolean. The runtime USB device is "active" when it is available to + interact with the host, it doesn't mean that a USB Host is actually + present. + + If the optional ``value`` argument is set to a truthy value, then + the USB device will be activated. + + If the optional ``value`` argument is set to a falsey value, then + the USB device is deactivated. While the USB device is deactivated, + it will not be detected by the USB Host. + + To simulate a disconnect and a reconnect of the USB device, call + ``active(False)`` followed by ``active(True)``. This may be + necessary if the runtime device configuration has changed, so that + the host sees the new device. + +.. attribute:: USDBD.builtin_driver + + This attribute holds the current built-in driver configuration, and must be + set to one of the ``USBDevice.BUILTIN_`` named constants defined on this object. + + By default it holds the value :data:`USBDevice.BUILTIN_NONE`. + + Runtime USB device must be inactive when setting this field. Call the + :func:`USBDevice.active` function to deactivate before setting if necessary + (and again to activate after setting). + + If this value is set to any value other than :data:`USBDevice.BUILTIN_NONE` then + the following restrictions apply to the :func:`USBDevice.config` arguments: + + - ``desc_cfg`` should begin with the built-in USB interface descriptor data + accessible via :data:`USBDevice.builtin_driver` attribute ``desc_cfg``. + Descriptors appended after the built-in configuration descriptors should use + interface, string and endpoint numbers starting from the max built-in values + defined in :data:`USBDevice.builtin_driver` attributes ``itf_max``, ``str_max`` and + ``ep_max``. + + - The ``bNumInterfaces`` field in the built-in configuration + descriptor will also need to be updated if any new interfaces + are appended to the end of ``desc_cfg``. + + - ``desc_strs`` should either be ``None`` or a list/dictionary where index + values less than ``USBDevice.builtin_driver.str_max`` are missing or have + value ``None``. This reserves those string indexes for the built-in + drivers. Placing a different string at any of these indexes overrides that + string in the built-in driver. + +.. method:: USBDevice.submit_xfer(self, ep, buffer /) + + Submit a USB transfer on endpoint number ``ep``. ``buffer`` must be + an object implementing the buffer interface, with read access for + ``IN`` endpoints and write access for ``OUT`` endpoints. + + .. note:: ``ep`` cannot be the control Endpoint number 0. Control + transfers are built up through successive executions of + ``control_xfer_cb``, see above. + + Returns ``True`` if successful, ``False`` if the transfer could not + be queued (as USB device is not configured by host, or because + another transfer is queued on this endpoint.) + + When the USB host completes the transfer, the ``xfer_cb`` callback + is called (see above). + + Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not + active. + +.. method:: USBDevice.stall(self, ep, [stall] /) + + Calling this function gets or sets the STALL state of a device endpoint. + + ``ep`` is the number of the endpoint. + + If the optional ``stall`` parameter is set, this is a boolean flag + for the STALL state. + + The return value is the current stall state of the endpoint (before + any change made by this function). + + An endpoint that is set to STALL may remain stalled until this + function is called again, or STALL may be cleared automatically by + the USB host. + + Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not + active. + +Constants +--------- + +.. data:: USBDevice.BUILTIN_NONE +.. data:: USBDevice.BUILTIN_DEFAULT +.. data:: USBDevice.BUILTIN_CDC +.. data:: USBDevice.BUILTIN_MSC +.. data:: USBDevice.BUILTIN_CDC_MSC + + These constant objects hold the built-in descriptor data which is + compiled into the MicroPython firmware. ``USBDevice.BUILTIN_NONE`` and + ``USBDevice.BUILTIN_DEFAULT`` are always present. Additional objects may be present + depending on the firmware build configuration and the actual built-in drivers. + + .. note:: Currently at most one of ``USBDevice.BUILTIN_CDC``, + ``USBDevice.BUILTIN_MSC`` and ``USBDevice.BUILTIN_CDC_MSC`` is defined + and will be the same object as ``USBDevice.BUILTIN_DEFAULT``. + These constants are defined to allow run-time detection of + the built-in driver (if any). Support for selecting one of + multiple built-in driver configurations may be added in the + future. + + These values are assigned to :data:`USBDevice.builtin_driver` to get/set the + built-in configuration. + + Each object contains the following read-only fields: + + - ``itf_max`` - One more than the highest bInterfaceNumber value used + in the built-in configuration descriptor. + - ``ep_max`` - One more than the highest bEndpointAddress value used + in the built-in configuration descriptor. Does not include any + ``IN`` flag bit (0x80). + - ``str_max`` - One more than the highest string descriptor index + value used by any built-in descriptor. + - ``desc_dev`` - ``bytes`` object containing the built-in USB device + descriptor. + - ``desc_cfg`` - ``bytes`` object containing the complete built-in USB + configuration descriptor. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 3f5cd6f13c..532266d1d9 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -265,3 +265,4 @@ Classes machine.WDT.rst machine.SD.rst machine.SDCard.rst + machine.USBDevice.rst diff --git a/docs/library/openamp.rst b/docs/library/openamp.rst new file mode 100644 index 0000000000..de4d51ad06 --- /dev/null +++ b/docs/library/openamp.rst @@ -0,0 +1,115 @@ +:mod:`openamp` -- provides standard Asymmetric Multiprocessing (AMP) support +============================================================================ + +.. module:: openamp + :synopsis: provides standard Asymmetric Multiprocessing (AMP) support + +The ``openamp`` module provides a standard inter-processor communications infrastructure +for MicroPython. The module handles all of the details of OpenAMP, such as setting up +the shared resource table, initializing vrings, etc. It provides an API for using the +RPMsg bus infrastructure with the `Endpoint` class, and provides processor Life Cycle +Management (LCM) support, such as loading firmware and starting and stopping a remote +core, via the `RemoteProc` class. + +Example usage:: + + import openamp + + def ept_recv_callback(src, data): + print("Received message on endpoint", data) + + # Create a new RPMsg endpoint to communicate with the remote core. + ept = openamp.Endpoint("vuart-channel", callback=ept_recv_callback) + + # Create a RemoteProc object, load its firmware and start it. + rproc = openamp.RemoteProc("virtual_uart.elf") # Or entry point address (ex 0x081E0000) + rproc.start() + + while True: + if ept.is_ready(): + ept.send("data") + +Functions +--------- + +.. function:: new_service_callback(ns_callback) + + Set the new service callback. + + The *ns_callback* argument is a function that will be called when the remote processor + announces new services. At that point the host processor can choose to create the + announced endpoint, if this particular service is supported, or ignore it if it's + not. If this function is not set, the host processor should first register the + endpoint locally, and it will be automatically bound when the remote announces + the service. + +Endpoint class +-------------- + +.. class:: Endpoint(name, callback, src=ENDPOINT_ADDR_ANY, dest=ENDPOINT_ADDR_ANY) + + Construct a new RPMsg Endpoint. An endpoint is a bidirectional communication + channel between two cores. + + Arguments are: + + - *name* is the name of the endpoint. + - *callback* is a function that is called when the endpoint receives data with the + source address of the remote point, and the data as bytes passed by reference. + - *src* is the endpoint source address. If none is provided one will be assigned + to the endpoint by the library. + - *dest* is the endpoint destination address. If the endpoint is created from the + new_service_callback, this must be provided and it must match the remote endpoint's + source address. If the endpoint is registered locally, before the announcement, the + destination address will be assigned by the library when the endpoint is bound. + +.. method:: Endpoint.deinit() + + Destroy the endpoint and release all of its resources. + +.. method:: Endpoint.is_ready() + + Returns True if the endpoint is ready to send (i.e., has both a source and destination addresses) + +.. method:: Endpoint.send(src=-1, dest=-1, timeout=-1) + + Send a message to the remote processor over this endpoint. + + Arguments are: + + - *src* is the source endpoint address of the message. If none is provided, the + source address the endpoint is bound to is used. + - *dest* is the destination endpoint address of the message. If none is provided, + the destination address the endpoint is bound to is used. + - *timeout* specifies the time in milliseconds to wait for a free buffer. By default + the function is blocking. + +RemoteProc class +---------------- + +.. class:: RemoteProc(entry) + + The RemoteProc object provides processor Life Cycle Management (LCM) support, such as + loading firmware, starting and stopping a remote core. + + The *entry* argument can be a path to firmware image, in which case the firmware is + loaded from file to its target memory, or an entry point address, in which case the + firmware must be loaded already at the given address. + +.. method:: RemoteProc.start() + + Starts the remote processor. + +.. method:: RemoteProc.stop() + + Stops the remote processor. The exact behavior is platform-dependent. On the STM32H7 for + example it's not possible to stop and then restart the Cortex-M4 core, so a complete + system reset is performed on a call to this function. + +.. method:: RemoteProc.shutdown() + + Shutdown stops the remote processor and releases all of its resources. The exact behavior + is platform-dependent, however typically it disables power and clocks to the remote core. + This function is also used as the finaliser (i.e., called when ``RemoteProc`` object is + collected). Note that on the STM32H7, it's not possible to stop and then restart the + Cortex-M4 core, so a complete system reset is performed on a call to this function. diff --git a/docs/library/rp2.DMA.rst b/docs/library/rp2.DMA.rst new file mode 100644 index 0000000000..c5e3f31aa2 --- /dev/null +++ b/docs/library/rp2.DMA.rst @@ -0,0 +1,293 @@ +.. currentmodule:: rp2 +.. _rp2.DMA: + +class DMA -- access to the RP2040's DMA controller +================================================== + +The :class:`DMA` class offers access to the RP2040's Direct Memory Access (DMA) +controller, providing the ability move data between memory blocks and/or IO registers. The DMA +controller has its own, separate read and write bus master connections onto the bus fabric and +each DMA channel can independently read data from one address and write it back to another +address, optionally incrementing one or both pointers, allowing it to perform transfers on behalf +of the processor while the processor carries out other tasks or enters a low power state. The +RP2040's DMA controller has 12 independent DMA channels that can run concurrently. For full +details of the RP2040's DMA system see section 2.5 of the `RP2040 Datasheet +`_. + +Examples +-------- + +The simplest use of the DMA controller is to move data from one block of memory to another. +This can be accomplished with the following code:: + + a = bytearray(32*1024) + b = bytearray(32*1024) + d = rp2.DMA() + c = d.pack_ctrl() # Just use the default control value. + # The count is in 'transfers', which defaults to four-byte words, so divide length by 4 + d.config(read=a, write=b, count=len(a)//4, ctrl=c, trigger=True) + # Wait for completion + while d.active(): + pass + +Note that while this example sits in an idle loop while it waits for the transfer to complete, +the program could just as well do some useful work in this time instead. + +Another, perhaps more common use of the DMA controller is to transfer between memory and an IO +peripheral. In this situation the address of the IO register does not change for each transfer but +the memory address needs to be incremented. It is also necessary to control the pace of the +transfer so as to not write data before it can be accepted by a peripheral or read it before the +data is ready, and this can be controlled with the ``treq_sel`` field of the DMA channel's control +register. The various fields of the control register for each DMA channel can be packed +using the :meth:`DMA.pack_ctrl()` method and unpacked using the :meth:`DMA.unpack_ctrl()` +static method. Code to transfer data from a byte array to the TX FIFO of a PIO state machine, +one byte at a time, looks like this:: + + # pio_num is index of the PIO block being used, sm_num is the state machine in that block. + # my_state_machine is an rp2.PIO() instance. + DATA_REQUEST_INDEX = (pio_num << 3) + sm_num + + src_data = bytearray(1024) + d = rp2.DMA() + + # Transfer bytes, rather than words, don't increment the write address and pace the transfer. + c = d.pack_ctrl(size=0, inc_write=False, treq_sel=DATA_REQUEST_INDEX) + + d.config( + read=src_data, + write=my_state_machine, + count=len(src_data), + ctrl=c, + trigger=True + ) + +Note that in this example the value given for the write address is just the PIO state machine to +which we are sending the data. This works because PIO state machines present the buffer protocol, +allowing direct access to their data FIFO registers. + +Constructor +----------- + +.. class:: DMA() + + Claim one of the DMA controller channels for exclusive use. + +Methods +------- + +.. method:: DMA.config(read=None, write=None, count=None, ctrl=None, trigger=False) + + Configure the DMA registers for the channel and optionally start the transfer. + Parameters are: + + - *read*: The address from which the DMA controller will start reading data or + an object that will provide data to be read. It can be an integer or any + object that supports the buffer protocol. + - *write*: The address to which the DMA controller will start writing or an + object into which data will be written. It can be an integer or any object + that supports the buffer protocol. + - *count*: The number of bus transfers that will execute before this channel + stops. Note that this is the number of transfers, not the number of bytes. + If the transfers are 2 or 4 bytes wide then the total amount of data moved + (and thus the size of required buffer) needs to be multiplied accordingly. + - *ctrl*: The value for the DMA control register. This is an integer value + that is typically packed using the :meth:`DMA.pack_ctrl()`. + - *trigger*: Optionally commence the transfer immediately. + +.. method:: DMA.irq(handler=None, hard=False) + + Returns the IRQ object for this DMA channel and optionally configures it. + +.. method:: DMA.close() + + Release the claim on the underlying DMA channel and free the interrupt + handler. The :class:`DMA` object can not be used after this operation. + +.. method:: DMA.pack_ctrl(default=None, **kwargs) + + Pack the values provided in the keyword arguments into the named fields of a new control + register value. Any field that is not provided will be set to a default value. The + default will either be taken from the provided ``default`` value, or if that is not + given, a default suitable for the current channel; setting this to the current value + of the `DMA.ctrl` attribute provides an easy way to override a subset of the fields. + + The keys for the keyword arguments can be any key returned by the :meth:`DMA.unpack_ctrl()` + method. The writable values are: + + - *enable*: ``bool`` Set to enable the channel (default: ``True``). + + - *high_pri*: ``bool`` Make this channel's bus traffic high priority (default: ``False``). + + - *size*: ``int`` Transfer size: 0=byte, 1=half word, 2=word (default: 2). + + - *inc_read*: ``bool`` Increment the read address after each transfer (default: ``True``). + + - *inc_write*: ``bool`` Increment the write address after each transfer (default: ``True``). + + - *ring_size*: ``int`` If non-zero, only the bottom ``ring_size`` bits of one + address register will change when an address is incremented, causing the + address to wrap at the next ``1 << ring_size`` byte boundary. Which + address is wrapped is controlled by the ``ring_sel`` flag. A zero value + disables address wrapping. + + - *ring_sel*: ``bool`` Set to ``False`` to have the ``ring_size`` apply to the read address + or ``True`` to apply to the write address. + + - *chain_to*: ``int`` The channel number for a channel to trigger after this transfer + completes. Setting this value to this DMA object's own channel number + disables chaining (this is the default). + + - *treq_sel*: ``int`` Select a Transfer Request signal. See section 2.5.3 in the RP2040 + datasheet for details. + + - *irq_quiet*: ``bool`` Do not generate interrupt at the end of each transfer. Interrupts + will instead be generated when a zero value is written to the trigger + register, which will halt a sequence of chained transfers (default: + ``True``). + + - *bswap*: ``bool`` If set to true, bytes in words or half-words will be reversed before + writing (default: ``True``). + + - *sniff_en*: ``bool`` Set to ``True`` to allow data to be accessed by the chips sniff + hardware (default: ``False``). + + - *write_err*: ``bool`` Setting this to ``True`` will clear a previously reported write + error. + + - *read_err*: ``bool`` Setting this to ``True`` will clear a previously reported read + error. + + See the description of the ``CH0_CTRL_TRIG`` register in section 2.5.7 of the RP2040 + datasheet for details of all of these fields. + +.. method:: DMA.unpack_ctrl(value) + + Unpack a value for a DMA channel control register into a dictionary with key/value pairs + for each of the fields in the control register. *value* is the ``ctrl`` register value + to unpack. + + This method will return values for all the keys that can be passed to ``DMA.pack_ctrl``. + In addition, it will also return the read-only flags in the control register: ``busy``, + which goes high when a transfer starts and low when it ends, and ``ahb_err``, which is + the logical OR of the ``read_err`` and ``write_err`` flags. These values will be ignored + when packing, so that the dictionary created by unpacking a control register can be used + directly as the keyword arguments for packing. + +.. method:: DMA.active([value]) + + Gets or sets whether the DMA channel is currently running. + + >>> sm.active() + 0 + >>> sm.active(1) + >>> while sm.active(): + ... pass + +Attributes +---------- + +.. attribute:: DMA.read + + This attribute reflects the address from which the next bus transfer + will read. It may be written with either an integer or an object + that supports the buffer protocol and doing so has immediate effect. + +.. attribute:: DMA.write + + This attribute reflects the address to which the next bus transfer + will write. It may be written with either an integer or an object + that supports the buffer protocol and doing so has immediate effect. + +.. attribute:: DMA.count + + Reading this attribute will return the number of remaining bus + transfers in the *current* transfer sequence. Writing this attribute + sets the total number of transfers to be the *next* transfer sequence. + +.. attribute:: DMA.ctrl + + This attribute reflects DMA channel control register. It is typically written + with an integer packed using the :meth:`DMA.pack_ctrl()` method. The returned + register value can be unpacked using the :meth:`DMA.unpack_ctrl()` method. + +.. attribute:: DMA.channel + + The channel number of the DMA channel. This can be passed in the ``chain_to`` + argument of `DMA.pack_ctrl()` on another channel to allow DMA chaining. + +.. attribute:: DMA.registers + + This attribute is an array-like object that allows direct access to + the DMA channel's registers. The index is by word, rather than by byte, + so the register indices are the register address offsets divided by 4. + See the RP2040 data sheet for register details. + +Chaining and trigger register access +------------------------------------ + +The DMA controller in the RP2040 offers a couple advanced features to allow one DMA channel +to initiate a transfer on another channel. One is the use of the ``chain_to`` value in the +control register and the other is writing to one of the DMA channel's registers that has a +trigger effect. When coupled with the ability to have one DMA channel write directly to the +`DMA.registers` of another channel, this allows for complex transactions to be performed +without any CPU intervention. + +Below is an example of using both chaining and register +triggering to implement gathering of multiple blocks of data into a single destination. Full +details of these features can be found in section 2.5 of the RP2040 data sheet and the code +below is a Pythonic version of the example in sub-section 2.5.6.2. + +.. code-block:: python + + from rp2 import DMA + from uctypes import addressof + from array import array + + def gather_strings(string_list, buf): + # We use two DMA channels. The first sends lengths and source addresses from the gather + # list to the registers of the second. The second copies the data itself. + gather_dma = DMA() + buffer_dma = DMA() + + # Pack up length/address pairs to be sent to the registers. + gather_list = array("I") + + for s in string_list: + gather_list.append(len(s)) + gather_list.append(addressof(s)) + + gather_list.append(0) + gather_list.append(0) + + # When writing to the registers of the second DMA channel, we need to wrap the + # write address on an 8-byte (1<<3 bytes) boundary. We write to the ``TRANS_COUNT`` + # and ``READ_ADD_TRIG`` registers in the last register alias (registers 14 and 15). + gather_ctrl = gather_dma.pack_ctrl(ring_size=3, ring_sel=True) + gather_dma.config( + read=gather_list, write=buffer_dma.registers[14:16], + count=2, ctrl=gather_ctrl + ) + + # When copying the data, the transfer size is single bytes, and when completed we need + # to chain back to the start another gather DMA transaction. + buffer_ctrl = buffer_dma.pack_ctrl(size=0, chain_to=gather_dma.channel) + # The read and count values will be set by the other DMA channel. + buffer_dma.config(write=buf, ctrl=buffer_ctrl) + + # Set the transfer in motion. + gather_dma.active(1) + + # Wait until all the register values have been sent + end_address = addressof(gather_list) + 4 * len(gather_list) + while gather_dma.read != end_address: + pass + + input = ["This is ", "a ", "test", " of the scatter", " gather", " process"] + output = bytearray(64) + + print(output) + gather_strings(input, output) + print(output) + +This example idles while waiting for the transfer to complete; alternatively it could +set an interrupt handler and return immediately. diff --git a/docs/library/rp2.StateMachine.rst b/docs/library/rp2.StateMachine.rst index e8c167c09f..1cb87e90b6 100644 --- a/docs/library/rp2.StateMachine.rst +++ b/docs/library/rp2.StateMachine.rst @@ -140,3 +140,10 @@ Methods Optionally configure it. +Buffer protocol +--------------- + +The StateMachine class supports the `buffer protocol`, allowing direct access to the transmit +and receive FIFOs for each state machine. This is primarily in order to allow StateMachine +objects to be passed directly as the read or write parameters when configuring a `rp2.DMA()` +channel. diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst index 7a473387b4..f0189327df 100644 --- a/docs/library/rp2.rst +++ b/docs/library/rp2.rst @@ -241,6 +241,7 @@ Classes .. toctree:: :maxdepth: 1 + rp2.DMA.rst rp2.Flash.rst rp2.PIO.rst rp2.StateMachine.rst diff --git a/docs/library/struct.rst b/docs/library/struct.rst index 026cb5e8ac..c235750596 100644 --- a/docs/library/struct.rst +++ b/docs/library/struct.rst @@ -45,6 +45,8 @@ The following data types are supported: +--------+--------------------+-------------------+---------------+ | Q | unsigned long long | integer (`1`) | 8 | +--------+--------------------+-------------------+---------------+ +| e | n/a (half-float) | float (`2`) | 2 | ++--------+--------------------+-------------------+---------------+ | f | float | float (`2`) | 4 | +--------+--------------------+-------------------+---------------+ | d | double | float (`2`) | 8 | diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index d795102b4a..218ec15a2a 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -11,9 +11,10 @@ SRC = btree_c.c btree_py.py ARCH = x64 BTREE_DIR = $(MPY_DIR)/lib/berkeley-db-1.xx -BTREE_DEFS = -D__DBINTERFACE_PRIVATE=1 -Dmpool_error="(void)" -Dabort=abort_ "-Dvirt_fd_t=void*" $(BTREE_DEFS_EXTRA) -CFLAGS += -I$(BTREE_DIR)/PORT/include -CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter $(BTREE_DEFS) +BERKELEY_DB_CONFIG_FILE ?= \"extmod/berkeley-db/berkeley_db_config_port.h\" +CFLAGS += -I$(BTREE_DIR)/include +CFLAGS += -DBERKELEY_DB_CONFIG_FILE=$(BERKELEY_DB_CONFIG_FILE) +CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter SRC += $(addprefix $(realpath $(BTREE_DIR))/,\ btree/bt_close.c \ diff --git a/examples/natmod/btree/btree_c.c b/examples/natmod/btree/btree_c.c index 3c60e41b2d..bbf51c731f 100644 --- a/examples/natmod/btree/btree_c.c +++ b/examples/natmod/btree/btree_c.c @@ -39,6 +39,10 @@ void abort_(void) { nlr_raise(mp_obj_new_exception(mp_load_global(MP_QSTR_RuntimeError))); } +int puts(const char *s) { + return mp_printf(&mp_plat_print, "%s\n", s); +} + int native_errno; #if defined(__linux__) int *__errno_location (void) diff --git a/extmod/berkeley-db/berkeley_db_config_port.h b/extmod/berkeley-db/berkeley_db_config_port.h new file mode 100644 index 0000000000..41e4acd81e --- /dev/null +++ b/extmod/berkeley-db/berkeley_db_config_port.h @@ -0,0 +1,16 @@ +// Berkeley-db configuration. + +#define __DBINTERFACE_PRIVATE 1 +#define mpool_error printf +#define abort abort_ +#define virt_fd_t void* + +#ifdef MICROPY_BERKELEY_DB_DEFPSIZE +#define DEFPSIZE MICROPY_BERKELEY_DB_DEFPSIZE +#endif + +#ifdef MICROPY_BERKELEY_DB_MINCACHE +#define MINCACHE MICROPY_BERKELEY_DB_MINCACHE +#endif + +__attribute__((noreturn)) void abort_(void); diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 53c7740132..60f1a23ad6 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 f0428bcd05..f7c6f9988e 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 0000000000..852b042898 --- /dev/null +++ b/extmod/libmetal/libmetal.mk @@ -0,0 +1,51 @@ +# Makefile directives for libmetal + +# libmetal is intended to run through a pre-processor (as part of its CMake-based build system). +# This replicates the basic functionality of the pre-processor, including adding a "micropython" +# platform that is almost identical to the built-in "generic" platform with a few small changes +# provided by the files in extmod/libmetal. +$(BUILD)/openamp: $(BUILD) + $(MKDIR) -p $@ + +$(BUILD)/openamp/metal: $(BUILD)/openamp + $(MKDIR) -p $@ + +$(BUILD)/openamp/metal/config.h: $(BUILD)/openamp/metal $(TOP)/$(LIBMETAL_DIR)/lib/config.h + @$(ECHO) "GEN $@" + @for file in $(TOP)/$(LIBMETAL_DIR)/lib/*.c $(TOP)/$(LIBMETAL_DIR)/lib/*.h; do $(SED) -e "s/@PROJECT_SYSTEM@/micropython/g" -e "s/@PROJECT_PROCESSOR@/arm/g" $$file > $(BUILD)/openamp/metal/$$(basename $$file); done + $(MKDIR) -p $(BUILD)/openamp/metal/processor/arm + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/processor/arm/*.h $(BUILD)/openamp/metal/processor/arm + $(MKDIR) -p $(BUILD)/openamp/metal/compiler/gcc + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/compiler/gcc/*.h $(BUILD)/openamp/metal/compiler/gcc + $(MKDIR) -p $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/system/generic/*.c $(TOP)/$(LIBMETAL_DIR)/lib/system/generic/*.h $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/extmod/libmetal/metal/system/micropython/* $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/extmod/libmetal/metal/config.h $(BUILD)/openamp/metal/config.h + +# libmetal's source files. +SRC_LIBMETAL_C := $(addprefix $(BUILD)/openamp/metal/,\ + device.c \ + dma.c \ + init.c \ + io.c \ + irq.c \ + log.c \ + shmem.c \ + softirq.c \ + version.c \ + device.c \ + system/micropython/condition.c \ + system/micropython/device.c \ + system/micropython/io.c \ + system/micropython/irq.c \ + system/micropython/shmem.c \ + system/micropython/time.c \ + ) + +# These files are generated by the rule above (along with config.h), so we need to make the .c +# files depend on that step -- can't use the .o files as the target because the .c files don't +# exist yet. +$(SRC_LIBMETAL_C): $(BUILD)/openamp/metal/config.h + +# Qstr processing will include the generated libmetal headers, so add them as a qstr requirement. +QSTR_GLOBAL_REQUIREMENTS += $(BUILD)/openamp/metal/config.h diff --git a/extmod/libmetal/metal/config.h b/extmod/libmetal/metal/config.h new file mode 100644 index 0000000000..459a2f4ca4 --- /dev/null +++ b/extmod/libmetal/metal/config.h @@ -0,0 +1,81 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * MicroPython libmetal config. + */ +#ifndef MICROPY_INCLUDED_METAL_MICROPYTHON_H +#define MICROPY_INCLUDED_METAL_MICROPYTHON_H + +#include + +// Port-specific config. +#include "mpmetalport.h" + +// Library major version number. +#define METAL_VER_MAJOR 1 + +// Library minor version number. +#define METAL_VER_MINOR 5 + +// Library patch level. +#define METAL_VER_PATCH 0 + +// Library version string. +#define METAL_VER "1.5.0" + +#if METAL_HAVE_STDATOMIC_H +#define HAVE_STDATOMIC_H +#endif + +#if METAL_HAVE_FUTEX_H +#define HAVE_FUTEX_H +#endif + +#ifndef METAL_MAX_DEVICE_REGIONS +#define METAL_MAX_DEVICE_REGIONS 1 +#endif + +// generic/log.h +#if METAL_LOG_HANDLER_ENABLE +#include "py/mphal.h" +#undef metal_log +#define metal_log(level, ...) mp_printf(&mp_plat_print, __VA_ARGS__) +#endif + +static inline void *__metal_allocate_memory(unsigned int size) { + return m_tracked_calloc(1, size); +} + +static inline void __metal_free_memory(void *ptr) { + m_tracked_free(ptr); +} + +// The following functions must be provided by the port (in mpmetalport.h / mpmetalport.c). +int __metal_sleep_usec(unsigned int usec); +void sys_irq_enable(unsigned int vector); +void sys_irq_disable(unsigned int vector); +void sys_irq_restore_enable(unsigned int flags); +unsigned int sys_irq_save_disable(void); +#endif // MICROPY_INCLUDED_METAL_MICROPYTHON_H diff --git a/extmod/libmetal/metal/system/micropython/alloc.h b/extmod/libmetal/metal/system/micropython/alloc.h new file mode 100644 index 0000000000..dd0fdb13f9 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/alloc.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/log.h b/extmod/libmetal/metal/system/micropython/log.h new file mode 100644 index 0000000000..dd0fdb13f9 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/log.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/sleep.h b/extmod/libmetal/metal/system/micropython/sleep.h new file mode 100644 index 0000000000..dd0fdb13f9 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/sleep.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/sys.h b/extmod/libmetal/metal/system/micropython/sys.h new file mode 100644 index 0000000000..0f404dcd61 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/sys.h @@ -0,0 +1,5 @@ +#include + +struct metal_state { + struct metal_common_state common; +}; diff --git a/extmod/machine_mem.c b/extmod/machine_mem.c index c8f0889b9a..c34ece2454 100644 --- a/extmod/machine_mem.c +++ b/extmod/machine_mem.c @@ -27,7 +27,7 @@ #include "py/runtime.h" #include "extmod/modmachine.h" -#if MICROPY_PY_MACHINE +#if MICROPY_PY_MACHINE_MEMX // If you wish to override the functions for mapping the machine_mem read/write // address, then add a #define for MICROPY_MACHINE_MEM_GET_READ_ADDR and/or @@ -113,4 +113,4 @@ const machine_mem_obj_t machine_mem8_obj = {{&machine_mem_type}, 1}; const machine_mem_obj_t machine_mem16_obj = {{&machine_mem_type}, 2}; const machine_mem_obj_t machine_mem32_obj = {{&machine_mem_type}, 4}; -#endif // MICROPY_PY_MACHINE +#endif // MICROPY_PY_MACHINE_MEMX diff --git a/extmod/machine_signal.c b/extmod/machine_signal.c index 6295a496b3..d2e7bc58ca 100644 --- a/extmod/machine_signal.c +++ b/extmod/machine_signal.c @@ -24,12 +24,11 @@ * THE SOFTWARE. */ -#include "py/mpconfig.h" -#if MICROPY_PY_MACHINE - #include - #include "py/runtime.h" + +#if MICROPY_PY_MACHINE_SIGNAL + #include "extmod/modmachine.h" #include "extmod/virtpin.h" @@ -181,4 +180,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &signal_locals_dict ); -#endif // MICROPY_PY_MACHINE +#endif // MICROPY_PY_MACHINE_SIGNAL diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c new file mode 100644 index 0000000000..69c3f38487 --- /dev/null +++ b/extmod/machine_usb_device.c @@ -0,0 +1,335 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Angus Gratton + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mpconfig.h" + +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +#include "mp_usbd.h" +#include "py/mperrno.h" +#include "py/objstr.h" + +// Implements the singleton runtime USB object +// +// Currently this implementation references TinyUSB directly. + +#ifndef NO_QSTR +#include "device/usbd_pvt.h" +#endif + +#define HAS_BUILTIN_DRIVERS (MICROPY_HW_USB_CDC || MICROPY_HW_USB_MSC) + +const mp_obj_type_t machine_usb_device_type; + +static mp_obj_t usb_device_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type; + (void)n_args; + (void)n_kw; + (void)args; + + if (MP_STATE_VM(usbd) == MP_OBJ_NULL) { + mp_obj_usb_device_t *o = m_new0(mp_obj_usb_device_t, 1); + o->base.type = &machine_usb_device_type; + o->desc_dev = mp_const_none; + o->desc_cfg = mp_const_none; + o->desc_strs = mp_const_none; + o->open_itf_cb = mp_const_none; + o->reset_cb = mp_const_none; + o->control_xfer_cb = mp_const_none; + o->xfer_cb = mp_const_none; + for (int i = 0; i < CFG_TUD_ENDPPOINT_MAX; i++) { + o->xfer_data[i][0] = mp_const_none; + o->xfer_data[i][1] = mp_const_none; + } + o->builtin_driver = MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none); + o->active = false; // Builtin USB may be active already, but runtime is inactive + o->trigger = false; + o->control_data = MP_OBJ_TO_PTR(mp_obj_new_memoryview('B', 0, NULL)); + o->num_pend_excs = 0; + for (int i = 0; i < MP_USBD_MAX_PEND_EXCS; i++) { + o->pend_excs[i] = mp_const_none; + } + + MP_STATE_VM(usbd) = MP_OBJ_FROM_PTR(o); + } + + return MP_STATE_VM(usbd); +} + +// Utility helper to raise an error if USB device is not active +// (or if a change of active state is triggered but not processed.) +static void usb_device_check_active(mp_obj_usb_device_t *usbd) { + if (!usbd->active || usbd->trigger) { + mp_raise_OSError(MP_EINVAL); + } +} + +static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buffer) { + mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(self); + int ep_addr; + mp_buffer_info_t buf_info = { 0 }; + bool result; + + usb_device_check_active(usbd); + + // Unmarshal arguments, raises TypeError if invalid + ep_addr = mp_obj_get_int(ep); + mp_get_buffer_raise(buffer, &buf_info, ep_addr & TUSB_DIR_IN_MASK ? MP_BUFFER_READ : MP_BUFFER_RW); + + uint8_t ep_num = tu_edpt_number(ep_addr); + uint8_t ep_dir = tu_edpt_dir(ep_addr); + + if (ep_num == 0 || ep_num >= CFG_TUD_ENDPPOINT_MAX) { + // TinyUSB usbd API doesn't range check arguments, so this check avoids + // out of bounds array access, or submitting transfers on the control endpoint. + // + // This C layer doesn't otherwise keep track of which endpoints the host + // is aware of (or not). + mp_raise_ValueError("ep"); + } + + if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) { + mp_raise_OSError(MP_EBUSY); + } + + result = usbd_edpt_xfer(USBD_RHPORT, ep_addr, buf_info.buf, buf_info.len); + + if (result) { + // Store the buffer object until the transfer completes + usbd->xfer_data[ep_num][ep_dir] = buffer; + } + + return mp_obj_new_bool(result); +} +static MP_DEFINE_CONST_FUN_OBJ_3(usb_device_submit_xfer_obj, usb_device_submit_xfer); + +static mp_obj_t usb_device_active(size_t n_args, const mp_obj_t *args) { + mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]); + + bool result = usbd->active; + if (n_args == 2) { + bool value = mp_obj_is_true(args[1]); + + if (value != result) { + if (value + && !mp_usb_device_builtin_enabled(usbd) + && usbd->desc_dev == mp_const_none) { + // Only allow activating if config() has already been called to set some descriptors, or a + // built-in driver is enabled + mp_raise_OSError(MP_EINVAL); + } + + // Any change to active state is triggered here and processed + // from the TinyUSB task. + usbd->active = value; + usbd->trigger = true; + if (value) { + mp_usbd_init(); // Ensure TinyUSB has initialised by this point + } + mp_usbd_schedule_task(); + } + } + + return mp_obj_new_bool(result); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_active_obj, 1, 2, usb_device_active); + +static mp_obj_t usb_device_stall(size_t n_args, const mp_obj_t *args) { + mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]); + int epnum = mp_obj_get_int(args[1]); + + usb_device_check_active(self); + + mp_obj_t res = mp_obj_new_bool(usbd_edpt_stalled(USBD_RHPORT, epnum)); + + if (n_args == 3) { // Set stall state + mp_obj_t stall = args[2]; + if (mp_obj_is_true(stall)) { + usbd_edpt_stall(USBD_RHPORT, epnum); + } else { + usbd_edpt_clear_stall(USBD_RHPORT, epnum); + } + } + + return res; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_stall_obj, 2, 3, usb_device_stall); + +// Configure the singleton USB device with all of the relevant transfer and descriptor +// callbacks for dynamic devices. +static mp_obj_t usb_device_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(pos_args[0]); + + enum { ARG_desc_dev, ARG_desc_cfg, ARG_desc_strs, ARG_open_itf_cb, + ARG_reset_cb, ARG_control_xfer_cb, ARG_xfer_cb, ARG_active }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_desc_dev, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_desc_cfg, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_desc_strs, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_open_itf_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_reset_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_control_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Check descriptor arguments + mp_obj_t desc_dev = args[ARG_desc_dev].u_obj; + mp_obj_t desc_cfg = args[ARG_desc_cfg].u_obj; + mp_obj_t desc_strs = args[ARG_desc_strs].u_obj; + if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_dev), buffer)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_dev")); + } + if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_cfg), buffer)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_cfg")); + } + if (desc_strs != mp_const_none + && !MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_strs), subscr)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_strs")); + } + + self->desc_dev = desc_dev; + self->desc_cfg = desc_cfg; + self->desc_strs = desc_strs; + self->open_itf_cb = args[ARG_open_itf_cb].u_obj; + self->reset_cb = args[ARG_reset_cb].u_obj; + self->control_xfer_cb = args[ARG_control_xfer_cb].u_obj; + self->xfer_cb = args[ARG_xfer_cb].u_obj; + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(usb_device_config_obj, 1, usb_device_config); + +static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_dev_obj, + &mp_usbd_builtin_desc_dev, sizeof(tusb_desc_device_t)); + +#if HAS_BUILTIN_DRIVERS +// BUILTIN_DEFAULT Python object holds properties of the built-in USB configuration +// (i.e. values used by the C implementation of TinyUSB devices.) +static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_cfg_obj, + mp_usbd_builtin_desc_cfg, MP_USBD_BUILTIN_DESC_CFG_LEN); + +static const mp_rom_map_elem_t usb_device_builtin_default_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(USBD_ITF_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(USBD_EP_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(USBD_STR_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) }, + { MP_ROM_QSTR(MP_QSTR_desc_cfg), MP_ROM_PTR(&builtin_default_desc_cfg_obj) }, +}; +static MP_DEFINE_CONST_DICT(usb_device_builtin_default_dict, usb_device_builtin_default_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_usb_device_builtin_default, + MP_QSTR_BUILTIN_DEFAULT, + MP_TYPE_FLAG_NONE, + locals_dict, &usb_device_builtin_default_dict + ); +#endif // HAS_BUILTIN_DRIVERS + +// BUILTIN_NONE holds properties for no enabled built-in USB device support +static const mp_rom_map_elem_t usb_device_builtin_none_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) }, + { MP_ROM_QSTR(MP_QSTR_desc_cfg), mp_const_empty_bytes }, +}; +static MP_DEFINE_CONST_DICT(usb_device_builtin_none_dict, usb_device_builtin_none_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_usb_device_builtin_none, + MP_QSTR_BUILTIN_NONE, + MP_TYPE_FLAG_NONE, + locals_dict, &usb_device_builtin_none_dict + ); + +static const mp_rom_map_elem_t usb_device_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&usb_device_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_submit_xfer), MP_ROM_PTR(&usb_device_submit_xfer_obj) }, + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&usb_device_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_stall), MP_ROM_PTR(&usb_device_stall_obj) }, + + // Built-in driver constants + { MP_ROM_QSTR(MP_QSTR_BUILTIN_NONE), MP_ROM_PTR(&mp_type_usb_device_builtin_none) }, + + #if !HAS_BUILTIN_DRIVERS + // No builtin-in drivers, so BUILTIN_DEFAULT is BUILTIN_NONE + { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_none) }, + #else + { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + + // Specific driver constant names are to support future switching of built-in drivers, + // but currently only one is present and it maps directly to BUILTIN_DEFAULT + #if MICROPY_HW_USB_CDC && !MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #if MICROPY_HW_USB_MSC && !MICROPY_HW_USB_CDC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #if MICROPY_HW_USB_CDC && MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #endif // !HAS_BUILTIN_DRIVERS +}; +static MP_DEFINE_CONST_DICT(usb_device_locals_dict, usb_device_locals_dict_table); + +static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_obj_usb_device_t *self = MP_OBJ_TO_PTR(self_in); + if (dest[0] == MP_OBJ_NULL) { + // Load attribute. + if (attr == MP_QSTR_builtin_driver) { + dest[0] = self->builtin_driver; + } else { + // Continue lookup in locals_dict. + dest[1] = MP_OBJ_SENTINEL; + } + } else if (dest[1] != MP_OBJ_NULL) { + // Store attribute. + if (attr == MP_QSTR_builtin_driver) { + if (self->active) { + mp_raise_OSError(MP_EINVAL); // Need to deactivate first + } + // Note: this value should be one of the BUILTIN_nnn constants, + // but not checked here to save code size in a low level API + self->builtin_driver = dest[1]; + dest[0] = MP_OBJ_NULL; + } + } +} + +MP_DEFINE_CONST_OBJ_TYPE( + machine_usb_device_type, + MP_QSTR_USBDevice, + MP_TYPE_FLAG_NONE, + make_new, usb_device_make_new, + locals_dict, &usb_device_locals_dict, + attr, &usb_device_attr + ); + +MP_REGISTER_ROOT_POINTER(mp_obj_t usbd); + +#endif diff --git a/extmod/modbtree.c b/extmod/modbtree.c index f8c38e0ad4..55c13ac911 100644 --- a/extmod/modbtree.c +++ b/extmod/modbtree.c @@ -57,8 +57,8 @@ #undef CIRCLEQ_INSERT_TAIL #undef CIRCLEQ_REMOVE -#include -#include <../../btree/btree.h> +#include "berkeley-db/db.h" +#include "berkeley-db/btree.h" typedef struct _mp_obj_btree_t { mp_obj_base_t base; diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 607143bb7e..d4e25917a6 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -38,6 +38,7 @@ #if MICROPY_PY_LWIP #include "shared/netutils/netutils.h" +#include "modnetwork.h" #include "lwip/init.h" #include "lwip/tcp.h" @@ -353,8 +354,12 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { } } +#if LWIP_VERSION_MAJOR < 2 +#define IPADDR_STRLEN_MAX 46 +#endif mp_obj_t lwip_format_inet_addr(const ip_addr_t *ip, mp_uint_t port) { - char *ipstr = ipaddr_ntoa(ip); + char ipstr[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(ip, ipstr, sizeof(ipstr)); mp_obj_t tuple[2] = { tuple[0] = mp_obj_new_str(ipstr, strlen(ipstr)), tuple[1] = mp_obj_new_int(port), @@ -1750,7 +1755,11 @@ static mp_obj_t lwip_getaddrinfo(size_t n_args, const mp_obj_t *args) { state.status = 0; MICROPY_PY_LWIP_ENTER + #if LWIP_VERSION_MAJOR < 2 err_t ret = dns_gethostbyname(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state); + #else + err_t ret = dns_gethostbyname_addrtype(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state, mp_mod_network_prefer_dns_use_ip_version == 4 ? LWIP_DNS_ADDRTYPE_IPV4_IPV6 : LWIP_DNS_ADDRTYPE_IPV6_IPV4); + #endif MICROPY_PY_LWIP_EXIT switch (ret) { diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 90e2a38a73..2a7e315bbb 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 e6b08b3fc9..7c16ed302e 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -213,6 +213,7 @@ extern const mp_obj_type_t machine_signal_type; extern const mp_obj_type_t machine_spi_type; extern const mp_obj_type_t machine_timer_type; extern const mp_obj_type_t machine_uart_type; +extern const mp_obj_type_t machine_usbd_type; extern const mp_obj_type_t machine_wdt_type; #if MICROPY_PY_MACHINE_SOFTI2C @@ -230,6 +231,10 @@ extern const mp_machine_spi_p_t mp_machine_soft_spi_p; extern const mp_obj_dict_t mp_machine_spi_locals_dict; #endif +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +extern const mp_obj_type_t machine_usb_device_type; +#endif + #if defined(MICROPY_MACHINE_MEM_GET_READ_ADDR) uintptr_t MICROPY_MACHINE_MEM_GET_READ_ADDR(mp_obj_t addr_o, uint align); #endif diff --git a/extmod/modnetwork.c b/extmod/modnetwork.c index c8d2b9e3ff..aa237bd93c 100644 --- a/extmod/modnetwork.c +++ b/extmod/modnetwork.c @@ -141,10 +141,17 @@ mp_obj_t mod_network_hostname(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_network_hostname_obj, 0, 1, mod_network_hostname); +#if LWIP_VERSION_MAJOR >= 2 +MP_DEFINE_CONST_FUN_OBJ_KW(mod_network_ipconfig_obj, 0, mod_network_ipconfig); +#endif + static const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, { MP_ROM_QSTR(MP_QSTR_country), MP_ROM_PTR(&mod_network_country_obj) }, { MP_ROM_QSTR(MP_QSTR_hostname), MP_ROM_PTR(&mod_network_hostname_obj) }, + #if LWIP_VERSION_MAJOR >= 2 + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&mod_network_ipconfig_obj) }, + #endif // Defined per port in mpconfigport.h #ifdef MICROPY_PORT_NETWORK_INTERFACES diff --git a/extmod/modnetwork.h b/extmod/modnetwork.h index 0a7897faaa..2ff9ce09de 100644 --- a/extmod/modnetwork.h +++ b/extmod/modnetwork.h @@ -69,10 +69,18 @@ extern char mod_network_hostname_data[MICROPY_PY_NETWORK_HOSTNAME_MAX_LEN + 1]; mp_obj_t mod_network_hostname(size_t n_args, const mp_obj_t *args); #if MICROPY_PY_LWIP + +#include "lwip/init.h" + struct netif; void mod_network_lwip_init(void); void mod_network_lwip_poll_wrapper(uint32_t ticks_ms); mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args); +#if LWIP_VERSION_MAJOR >= 2 +mp_obj_t mod_network_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); +mp_obj_t mod_network_nic_ipconfig(struct netif *netif, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); +extern int mp_mod_network_prefer_dns_use_ip_version; +#endif #elif defined(MICROPY_PORT_NETWORK_INTERFACES) struct _mod_network_socket_obj_t; diff --git a/extmod/modopenamp.c b/extmod/modopenamp.c new file mode 100644 index 0000000000..eb19c4b737 --- /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 0000000000..8f677788f9 --- /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 0000000000..6f43c71546 --- /dev/null +++ b/extmod/modopenamp_remoteproc.c @@ -0,0 +1,175 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * OpenAMP's remoteproc class. + */ + +#if MICROPY_PY_OPENAMP_REMOTEPROC + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" + +#include "openamp/open_amp.h" +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +#include "modopenamp.h" +#include "modopenamp_remoteproc.h" + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +#if !MICROPY_PY_OPENAMP +#error "MICROPY_PY_OPENAMP_REMOTEPROC requires MICROPY_PY_OPENAMP" +#endif + +typedef struct openamp_remoteproc_obj { + mp_obj_base_t base; + struct remoteproc rproc; +} openamp_remoteproc_obj_t; + +const mp_obj_type_t openamp_remoteproc_type; + +// Port-defined image store operations. +extern struct image_store_ops openamp_remoteproc_store_ops; + +// Port-defined remote-proc operations. +const struct remoteproc_ops openamp_remoteproc_ops = { + .init = mp_openamp_remoteproc_init, + .mmap = mp_openamp_remoteproc_mmap, + .start = mp_openamp_remoteproc_start, + .stop = mp_openamp_remoteproc_stop, + .config = mp_openamp_remoteproc_config, + .remove = mp_openamp_remoteproc_remove, + .shutdown = mp_openamp_remoteproc_shutdown, +}; + +static mp_obj_t openamp_remoteproc_start(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Start the processor to run the application. + int error = remoteproc_start(&self->rproc); + if (error != 0) { + self->rproc.state = RPROC_ERROR; + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_start_obj, openamp_remoteproc_start); + +static mp_obj_t openamp_remoteproc_stop(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Stop the processor, but the processor is not powered down. + int error = remoteproc_stop(&self->rproc); + if (error != 0) { + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_stop_obj, openamp_remoteproc_stop); + +static mp_obj_t openamp_remoteproc_shutdown(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Shutdown the remoteproc and release its resources. + int error = remoteproc_shutdown(&self->rproc); + if (error != 0) { + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_shutdown_obj, openamp_remoteproc_shutdown); + +mp_obj_t openamp_remoteproc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_entry }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_entry, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + openamp_remoteproc_obj_t *self = mp_obj_malloc_with_finaliser(openamp_remoteproc_obj_t, &openamp_remoteproc_type); + + // Make sure OpenAMP is initialized. + if (MP_STATE_PORT(virtio_device) == NULL) { + openamp_init(); + } + + // Create a remoteproc instance. + // NOTE: ports should use rproc->priv to allocate the image store, + // which gets passed to remoteproc_load(), and all of the store ops. + remoteproc_init(&self->rproc, &openamp_remoteproc_ops, NULL); + + // Configure the remote before loading applications (optional). + int error = remoteproc_config(&self->rproc, NULL); + if (error != 0) { + mp_raise_OSError(error); + } + + if (mp_obj_is_int(args[ARG_entry].u_obj)) { + self->rproc.bootaddr = mp_obj_get_int(args[ARG_entry].u_obj); + } else { + #if MICROPY_PY_OPENAMP_REMOTEPROC_ELFLD_ENABLE + // Load firmware. + const char *path = mp_obj_str_get_str(args[ARG_entry].u_obj); + int error = remoteproc_load(&self->rproc, path, self->rproc.priv, &openamp_remoteproc_store_ops, NULL); + if (error != 0) { + mp_raise_OSError(error); + } + #else + mp_raise_TypeError(MP_ERROR_TEXT("loading firmware is not supported.")); + #endif + } + return MP_OBJ_FROM_PTR(self); +} + +static const mp_rom_map_elem_t openamp_remoteproc_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_RemoteProc) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&openamp_remoteproc_shutdown_obj) }, + { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&openamp_remoteproc_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&openamp_remoteproc_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_shutdown), MP_ROM_PTR(&openamp_remoteproc_shutdown_obj) }, +}; +static MP_DEFINE_CONST_DICT(openamp_remoteproc_dict, openamp_remoteproc_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + openamp_remoteproc_type, + MP_QSTR_RemoteProc, + MP_TYPE_FLAG_NONE, + make_new, openamp_remoteproc_make_new, + locals_dict, &openamp_remoteproc_dict + ); + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC diff --git a/shared/tinyusb/mp_usbd_internal.h b/extmod/modopenamp_remoteproc.h similarity index 55% rename from shared/tinyusb/mp_usbd_internal.h rename to extmod/modopenamp_remoteproc.h index 8b8f50bae8..9bc2b07064 100644 --- a/shared/tinyusb/mp_usbd_internal.h +++ b/extmod/modopenamp_remoteproc.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2022 Angus Gratton + * 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 @@ -22,13 +22,25 @@ * 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_SHARED_TINYUSB_MP_USBD_INTERNAL_H -#define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H -#include "tusb.h" +#ifndef MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H +#define MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_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 "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" -#endif // MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H +void *mp_openamp_remoteproc_store_alloc(void); +struct remoteproc *mp_openamp_remoteproc_init(struct remoteproc *rproc, + const struct remoteproc_ops *ops, void *arg); +void *mp_openamp_remoteproc_mmap(struct remoteproc *rproc, metal_phys_addr_t *pa, + metal_phys_addr_t *da, size_t size, unsigned int attribute, + struct metal_io_region **io); +int mp_openamp_remoteproc_start(struct remoteproc *rproc); +int mp_openamp_remoteproc_stop(struct remoteproc *rproc); +int mp_openamp_remoteproc_config(struct remoteproc *rproc, void *data); +void mp_openamp_remoteproc_remove(struct remoteproc *rproc); +int mp_openamp_remoteproc_shutdown(struct remoteproc *rproc); + +#endif // MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H diff --git a/extmod/modopenamp_remoteproc_store.c b/extmod/modopenamp_remoteproc_store.c new file mode 100644 index 0000000000..857c133469 --- /dev/null +++ b/extmod/modopenamp_remoteproc_store.c @@ -0,0 +1,146 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * OpenAMP's remoteproc store. + */ + +#if MICROPY_PY_OPENAMP_REMOTEPROC + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" + +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +#include "modopenamp.h" +#include "modopenamp_remoteproc.h" + +#if MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +// Note the initial file buffer size needs to be at least 512 to read +// enough of the elf headers on the first call to store_open(), and on +// subsequent calls to store functions, it gets reallocated if needed. +#define RPROC_FILE_STORE_BUF_SIZE (1024) + +typedef struct openamp_remoteproc_filestore { + size_t len; + uint8_t *buf; + mp_obj_t file; +} openamp_remoteproc_filestore_t; + +void *mp_openamp_remoteproc_store_alloc(void) { + // Allocate an rproc filestore. + openamp_remoteproc_filestore_t *fstore; + fstore = metal_allocate_memory(sizeof(openamp_remoteproc_filestore_t)); + fstore->len = RPROC_FILE_STORE_BUF_SIZE; + fstore->buf = metal_allocate_memory(RPROC_FILE_STORE_BUF_SIZE); + return fstore; +} + +static int openamp_remoteproc_store_open(void *store, const char *path, const void **image_data) { + DEBUG_printf("store_open(): %s\n", path); + mp_obj_t args[2] = { + mp_obj_new_str(path, strlen(path)), + MP_OBJ_NEW_QSTR(MP_QSTR_rb), + }; + + openamp_remoteproc_filestore_t *fstore = store; + fstore->file = mp_vfs_open(MP_ARRAY_SIZE(args), args, (mp_map_t *)&mp_const_empty_map); + + int error = 0; + mp_uint_t bytes = mp_stream_read_exactly(fstore->file, fstore->buf, RPROC_FILE_STORE_BUF_SIZE, &error); + if (error != 0 || bytes != RPROC_FILE_STORE_BUF_SIZE) { + return -EINVAL; + } + *image_data = fstore->buf; + return bytes; +} + +static void openamp_remoteproc_store_close(void *store) { + DEBUG_printf("store_close()\n"); + openamp_remoteproc_filestore_t *fstore = store; + mp_stream_close(fstore->file); + metal_free_memory(fstore->buf); + metal_free_memory(fstore); +} + +static int openamp_remoteproc_store_load(void *store, size_t offset, size_t size, + const void **data, metal_phys_addr_t pa, + struct metal_io_region *io, + char is_blocking) { + + int error = 0; + openamp_remoteproc_filestore_t *fstore = store; + + if (mp_stream_seek(fstore->file, offset, MP_SEEK_SET, &error) == -1) { + return -EINVAL; + } + + if (pa == METAL_BAD_PHYS) { + if (size > fstore->len) { + // Note tracked allocs don't support realloc. + fstore->len = size; + fstore->buf = metal_allocate_memory(size); + DEBUG_printf("store_load() realloc to %lu\n", fstore->len); + } + *data = fstore->buf; + DEBUG_printf("store_load(): pa 0x%lx offset %u size %u \n", (uint32_t)pa, offset, size); + } else { + void *va = metal_io_phys_to_virt(io, pa); + if (va == NULL) { + return -EINVAL; + } + *data = va; + DEBUG_printf("store_load(): pa 0x%lx va 0x%p offset %u size %u \n", (uint32_t)pa, va, offset, size); + } + + mp_uint_t bytes = mp_stream_read_exactly(fstore->file, (void *)*data, size, &error); + if (bytes != size || error != 0) { + return -EINVAL; + } + + return bytes; +} + +const struct image_store_ops openamp_remoteproc_store_ops = { + .open = openamp_remoteproc_store_open, + .close = openamp_remoteproc_store_close, + .load = openamp_remoteproc_store_load, + .features = SUPPORT_SEEK, +}; + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 89e68e2488..d3543c5260 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -321,6 +321,12 @@ static mp_obj_t network_cyw43_ifconfig(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_cyw43_ifconfig_obj, 1, 2, network_cyw43_ifconfig); +static mp_obj_t network_cyw43_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ipconfig(&self->cyw->netif[self->itf], n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_cyw43_ipconfig_obj, 1, network_cyw43_ipconfig); + static mp_obj_t network_cyw43_status(size_t n_args, const mp_obj_t *args) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); (void)self; @@ -532,6 +538,7 @@ static const mp_rom_map_elem_t network_cyw43_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_cyw43_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_cyw43_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_cyw43_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_cyw43_ipconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_cyw43_status_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_cyw43_config_obj) }, diff --git a/extmod/network_esp_hosted.c b/extmod/network_esp_hosted.c index 14e8be8b6a..f86eaad37c 100644 --- a/extmod/network_esp_hosted.c +++ b/extmod/network_esp_hosted.c @@ -204,6 +204,13 @@ static mp_obj_t network_esp_hosted_ifconfig(size_t n_args, const mp_obj_t *args) } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_esp_hosted_ifconfig_obj, 1, 2, network_esp_hosted_ifconfig); +static mp_obj_t network_esp_hosted_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + esp_hosted_obj_t *self = MP_OBJ_TO_PTR(args[0]); + void *netif = esp_hosted_wifi_get_netif(self->itf); + return mod_network_nic_ipconfig(netif, n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_esp_hosted_ipconfig_obj, 1, network_esp_hosted_ipconfig); + static mp_obj_t network_esp_hosted_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { esp_hosted_obj_t *self = MP_OBJ_TO_PTR(args[0]); @@ -299,6 +306,7 @@ static const mp_rom_map_elem_t network_esp_hosted_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_esp_hosted_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_esp_hosted_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_esp_hosted_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_esp_hosted_ipconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_esp_hosted_config_obj) }, { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_esp_hosted_status_obj) }, { MP_ROM_QSTR(MP_QSTR_OPEN), MP_ROM_INT(ESP_HOSTED_SEC_OPEN) }, diff --git a/extmod/network_lwip.c b/extmod/network_lwip.c index caa30f6fff..97cb43902d 100644 --- a/extmod/network_lwip.c +++ b/extmod/network_lwip.c @@ -26,6 +26,7 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "py/parsenum.h" #if MICROPY_PY_NETWORK && MICROPY_PY_LWIP @@ -40,9 +41,19 @@ #include "lwip/timeouts.h" #include "lwip/dns.h" #include "lwip/dhcp.h" +#include "lwip/nd6.h" +#include "lwip/dhcp6.h" +#include "lwip/prot/dhcp.h" +#include "lwip/prot/dhcp6.h" +#include +#include + +int mp_mod_network_prefer_dns_use_ip_version = 4; // Implementations of network methods that can be used by any interface. +// This function provides the implementation of nic.ifconfig, is deprecated and will be removed. +// Use network.ipconfig and nic.ipconfig instead. mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args) { if (n_args == 0) { // Get IP addresses @@ -90,6 +101,291 @@ mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_o } } +// This function provides the common implementation of network.ipconfig +mp_obj_t mod_network_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (kwargs->used == 0) { + // Get config value + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[0])) { + case MP_QSTR_dns: { + char addr_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(dns_getserver(0), addr_str, sizeof(addr_str)); + return mp_obj_new_str(addr_str, strlen(addr_str)); + } + case MP_QSTR_prefer: { + return MP_OBJ_NEW_SMALL_INT(mp_mod_network_prefer_dns_use_ip_version); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } else { + // Set config value(s) + if (n_args != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_dns: { + ip_addr_t dns; + size_t addr_len; + const char *addr_str = mp_obj_str_get_data(e->value, &addr_len); + if (!ipaddr_aton(addr_str, &dns)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments as dns server")); + } + dns_setserver(0, &dns); + break; + } + case MP_QSTR_prefer: { + int value = mp_obj_get_int(e->value); + if (value != 4 && value != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid prefer argument")); + } + mp_mod_network_prefer_dns_use_ip_version = value; + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + } + return mp_const_none; +} + +mp_obj_t mod_network_nic_ipconfig(struct netif *netif, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + + if (kwargs->used == 0) { + // Get config value + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[0])) { + case MP_QSTR_dhcp4: { + struct dhcp *dhcp = netif_dhcp_data(netif); + return mp_obj_new_bool(dhcp != NULL && dhcp->state != DHCP_STATE_OFF); + } + case MP_QSTR_has_dhcp4: { + return mp_obj_new_bool(dhcp_supplied_address(netif)); + } + #if LWIP_IPV6_DHCP6 + case MP_QSTR_dhcp6: { + struct dhcp6 *dhcp = netif_dhcp6_data(netif); + return mp_obj_new_bool(dhcp != NULL && dhcp->state != DHCP6_STATE_OFF); + } + #endif + #if LWIP_IPV6_AUTOCONFIG + case MP_QSTR_autoconf6: { + return netif->ip6_autoconfig_enabled ? mp_const_true : mp_const_false; + } + case MP_QSTR_has_autoconf6: { + int found = 0; + for (int i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + !netif_ip6_addr_isstatic(netif, i)) { + found = 1; + break; + } + } + if (found) { + break; + } + return mp_obj_new_bool(found); + } + #endif + case MP_QSTR_addr4: { + mp_obj_t tuple[2] = { + netutils_format_ipv4_addr((uint8_t *)&netif->ip_addr, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&netif->netmask, NETUTILS_BIG), + }; + return mp_obj_new_tuple(2, tuple); + } + #if LWIP_IPV6 + case MP_QSTR_addr6: { + mp_obj_t addrs[LWIP_IPV6_NUM_ADDRESSES]; + size_t n_addrs = 0; + for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { + char addr_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(netif_ip_addr6(netif, i), addr_str, sizeof(addr_str)); + mp_obj_t tuple[4] = { + mp_obj_new_str(addr_str, strlen(addr_str)), + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_state(netif, i)), + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_pref_life(netif, i)), // preferred + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_valid_life(netif, i)) + }; + addrs[n_addrs++] = mp_obj_new_tuple(4, tuple); + } + } + return mp_obj_new_list(n_addrs, addrs); + } + #endif + case MP_QSTR_gw4: { + return netutils_format_ipv4_addr((uint8_t *)&netif->gw, NETUTILS_BIG); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + return mp_const_none; + } else { + // Set config value(s) + if (n_args != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_dhcp4: { + if (mp_obj_is_true(e->value)) { + if (dhcp_supplied_address(netif)) { + dhcp_renew(netif); + } else { + dhcp_release_and_stop(netif); + dhcp_start(netif); + } + } else { + dhcp_release_and_stop(netif); + } + break; + } + #if LWIP_IPV6_DHCP6 + case MP_QSTR_dhcp6: { + dhcp6_disable(netif); + dhcp6_enable_stateless(netif); + break; + } + #endif + #if LWIP_IPV6_AUTOCONFIG + case MP_QSTR_autoconf6: { + netif_set_ip6_autoconfig_enabled(netif, mp_obj_is_true(e->value)); + if (mp_obj_is_true(e->value)) { + nd6_restart_netif(netif); + } else { + // Clear out any non-static addresses, skip link-local address in slot 0 + for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + !netif_ip6_addr_isstatic(netif, i)) { + netif_ip6_addr_set_state(netif, i, IP6_ADDR_INVALID); + } + } + } + break; + } + #endif + case MP_QSTR_addr4: + case MP_QSTR_addr6: { + ip_addr_t ip_addr; + ip_addr_t netmask; + int prefix_bits = 32; + if (e->value != mp_const_none && mp_obj_is_str(e->value)) { + size_t addr_len; + const char *input_str = mp_obj_str_get_data(e->value, &addr_len); + char plain_ip[IPADDR_STRLEN_MAX]; + char *split = strchr(input_str, '/'); + const char *addr_str = input_str; + if (split) { + int to_copy = sizeof(plain_ip) - 1; + if (split - addr_str < to_copy) { + to_copy = split - addr_str; + } + memcpy(plain_ip, addr_str, to_copy); + mp_obj_t prefix_obj = mp_parse_num_integer(split + 1, strlen(split + 1), 10, NULL); + prefix_bits = mp_obj_get_int(prefix_obj); + } + if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) { + uint32_t mask = -(1u << (32 - prefix_bits)); + ip_addr_set_ip4_u32_val(netmask, ((mask & 0xFF) << 24) | ((mask & 0xFF00) << 8) | ((mask >> 8) & 0xFF00) | ((mask >> 24) & 0xFF)); + } + if (!ipaddr_aton(addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if ((mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr6) != IP_IS_V6(&ip_addr) + || (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) != IP_IS_V4(&ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + } else if (e->value != mp_const_none) { + mp_obj_t *items; + mp_obj_get_array_fixed_n(e->value, 2, &items); + size_t addr_len; + const char *ip_addr_str = mp_obj_str_get_data(items[0], &addr_len); + const char *netmask_str = mp_obj_str_get_data(items[1], &addr_len); + if (!ipaddr_aton(ip_addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (!ipaddr_aton(netmask_str, &netmask)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (!IP_IS_V4(&ip_addr) || !IP_IS_V4(&netmask)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + } + if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) { + if (e->value != mp_const_none) { + netif->ip_addr = ip_addr; + netif->netmask = netmask; + } else { + ip4_addr_set_any(ip_2_ip4(&netif->ip_addr)); + ip4_addr_set_any(ip_2_ip4(&netif->netmask)); + } + #if LWIP_IPV6 + } else if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr6) { + // Clear out any existing static addresses. Address 0 comes from autoconf. + for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + netif_ip6_addr_isstatic(netif, i)) { + netif_ip6_addr_set_state(netif, i, IP6_ADDR_INVALID); + } + } + if (e->value != mp_const_none) { + s8_t free_idx; + netif_add_ip6_address(netif, ip_2_ip6(&ip_addr), &free_idx); + netif_ip6_addr_set_valid_life(netif, free_idx, IP6_ADDR_LIFE_STATIC); + netif_ip6_addr_set_pref_life(netif, free_idx, IP6_ADDR_LIFE_STATIC); + netif_ip6_addr_set_state(netif, free_idx, IP6_ADDR_PREFERRED); + } + #endif + } + break; + } + case MP_QSTR_gw4: { + ip_addr_t ip_addr; + size_t addr_len; + const char *addr_str = mp_obj_str_get_data(e->value, &addr_len); + if (!ipaddr_aton(addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (IP_IS_V4(&ip_addr)) { + netif->gw = ip_addr; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + } + return mp_const_none; +} + #endif // LWIP_VERSION_MAJOR >= 2 #endif // MICROPY_PY_NETWORK && MICROPY_PY_LWIP diff --git a/extmod/network_ninaw10.c b/extmod/network_ninaw10.c index e68a7a66e2..90cdafc7a4 100644 --- a/extmod/network_ninaw10.c +++ b/extmod/network_ninaw10.c @@ -153,7 +153,7 @@ static void network_ninaw10_poll_connect(mp_sched_node_t *node) { debug_printf("poll_connect() status: %d reason %d\n", status, reason); if (nina_connect(self->ssid, self->security, self->key, 0) != 0) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s\n"), + MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s"), self->ssid, self->security, self->key); } } else { @@ -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. diff --git a/extmod/network_wiznet5k.c b/extmod/network_wiznet5k.c index d1aadc3e15..b550f8c1f4 100644 --- a/extmod/network_wiznet5k.c +++ b/extmod/network_wiznet5k.c @@ -915,6 +915,12 @@ static mp_obj_t wiznet5k_ifconfig(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(wiznet5k_ifconfig_obj, 1, 2, wiznet5k_ifconfig); +static mp_obj_t network_wiznet5k_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + wiznet5k_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ipconfig(&self->netif, n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(wiznet5k_ipconfig_obj, 1, network_wiznet5k_ipconfig); + static mp_obj_t send_ethernet_wrapper(mp_obj_t self_in, mp_obj_t buf_in) { wiznet5k_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_buffer_info_t buf; @@ -1008,6 +1014,9 @@ static const mp_rom_map_elem_t wiznet5k_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&wiznet5k_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&wiznet5k_active_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&wiznet5k_ifconfig_obj) }, + #if WIZNET5K_WITH_LWIP_STACK + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&wiznet5k_ipconfig_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&wiznet5k_status_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&wiznet5k_config_obj) }, #if WIZNET5K_WITH_LWIP_STACK diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 92bc6764ed..8d555f1124 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -728,6 +728,9 @@ void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { } void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } switch (addr_mode) { case MP_BLUETOOTH_ADDRESS_MODE_PUBLIC: if (!has_public_address()) { diff --git a/extmod/os_dupterm.c b/extmod/os_dupterm.c index 156766a43c..399f2237fb 100644 --- a/extmod/os_dupterm.c +++ b/extmod/os_dupterm.c @@ -45,6 +45,10 @@ void mp_os_deactivate(size_t dupterm_idx, const char *msg, mp_obj_t exc) { if (exc != MP_OBJ_NULL) { mp_obj_print_exception(&mp_plat_print, exc); } + if (term == MP_OBJ_NULL) { + // Dupterm was already closed. + return; + } nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_stream_close(term); diff --git a/lib/berkeley-db-1.xx b/lib/berkeley-db-1.xx index 35aaec4418..85373b548f 160000 --- a/lib/berkeley-db-1.xx +++ b/lib/berkeley-db-1.xx @@ -1 +1 @@ -Subproject commit 35aaec4418ad78628a3b935885dd189d41ce779b +Subproject commit 85373b548f1fb0119a463582570b44189dfb09ae diff --git a/lib/libmetal b/lib/libmetal new file mode 160000 index 0000000000..0cb7d293a7 --- /dev/null +++ b/lib/libmetal @@ -0,0 +1 @@ +Subproject commit 0cb7d293a7f25394a06847a28d0f0ace9862936e diff --git a/lib/open-amp b/lib/open-amp new file mode 160000 index 0000000000..1904dee18d --- /dev/null +++ b/lib/open-amp @@ -0,0 +1 @@ +Subproject commit 1904dee18da85400e72b8f55c5c99e872a486573 diff --git a/ports/cc3200/mpconfigport.h b/ports/cc3200/mpconfigport.h index 9cbc1afc0b..f1ba4bedd0 100644 --- a/ports/cc3200/mpconfigport.h +++ b/ports/cc3200/mpconfigport.h @@ -127,6 +127,7 @@ #define MICROPY_PY_VFS (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/cc3200/mods/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_WDT (1) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index f004c78b0a..0afb12f85c 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -114,6 +114,7 @@ #define MICROPY_PY_OS_URANDOM (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/esp32/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index 32eb39a203..8af7aba0aa 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -38,7 +38,7 @@ MICROPY_ROM_TEXT_COMPRESSION ?= 1 MICROPY_PY_SSL = 1 MICROPY_SSL_AXTLS = 1 AXTLS_DEFS_EXTRA = -Dabort=abort_ -DRT_MAX_PLAIN_LENGTH=1024 -DRT_EXTRA=4096 -BTREE_DEFS_EXTRA = -DDEFPSIZE=1024 -DMINCACHE=3 +BTREE_DEFS_EXTRA = -DMICROPY_BERKELEY_DB_DEFPSIZE=1024 -DMICROPY_BERKELEY_DB_MINCACHE=3 FROZEN_MANIFEST ?= boards/manifest.py diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index bbfab64fba..6504127755 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -66,6 +66,7 @@ #define MICROPY_PY_LWIP_SOCK_RAW (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/esp8266/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 761c491742..dddf44e605 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -115,7 +115,9 @@ int main(void) { // Execute user scripts. int ret = pyexec_file_if_exists("boot.py"); + #if MICROPY_HW_ENABLE_USBDEV mp_usbd_init(); + #endif if (ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index c67b010c01..a4c6d6e206 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/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h index 94624be028..499abbc4ef 100644 --- a/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h +++ b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h index 343fa4c2f3..35b70d6129 100644 --- a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h +++ b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/boards/PCA10059/mpconfigboard.h b/ports/nrf/boards/PCA10059/mpconfigboard.h index 1c87731584..97f6ccb941 100644 --- a/ports/nrf/boards/PCA10059/mpconfigboard.h +++ b/ports/nrf/boards/PCA10059/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 30625382e4..37fbdf1eb3 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -174,6 +174,7 @@ #define MICROPY_PY_TIME (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/nrf/modules/machine/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_PULSE (0) diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index ebd055f2ee..52effd64f9 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -131,6 +131,7 @@ #ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/renesas-ra/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 392fed29b1..b605faaf21 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -108,6 +108,7 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/tinyusb/mp_cdc_common.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ) set(MICROPY_SOURCE_DRIVERS @@ -146,6 +147,7 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_DIR}/shared/readline/readline.c ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ${MICROPY_PORT_DIR}/machine_adc.c ${MICROPY_PORT_DIR}/machine_i2c.c ${MICROPY_PORT_DIR}/machine_pin.c diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 70a67066fc..40374faff9 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -221,6 +221,10 @@ int main(int argc, char **argv) { mp_thread_deinit(); #endif soft_timer_deinit(); + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif + gc_sweep_all(); mp_deinit(); } diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 8f073002e8..a29692d0be 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -52,6 +52,10 @@ #ifndef MICROPY_HW_USB_MSC #define MICROPY_HW_USB_MSC (0) #endif + +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +#endif #endif #ifndef MICROPY_CONFIG_ROM_LEVEL @@ -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 5a7cb9916a..b678cd9828 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -136,6 +136,7 @@ SHARED_SRC_C += \ shared/tinyusb/mp_cdc_common.c \ shared/tinyusb/mp_usbd.c \ shared/tinyusb/mp_usbd_descriptor.c \ + shared/tinyusb/mp_usbd_runtime.c \ ASF4_SRC_C += $(addprefix lib/asf4/$(MCU_SERIES_LOWER)/,\ hal/src/hal_atomic.c \ diff --git a/ports/samd/main.c b/ports/samd/main.c index f051e961ff..9f213f27d4 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -93,6 +93,9 @@ void samd_main(void) { pwm_deinit_all(); #endif soft_timer_deinit(); + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif gc_sweep_all(); #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART sercom_deinit_all(); diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 160442a400..0b47500bf7 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -63,8 +63,13 @@ #ifndef MICROPY_HW_USB_DESC_STR_MAX #define MICROPY_HW_USB_DESC_STR_MAX (32) #endif +// Support machine.USBDevice +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) #endif +#endif // MICROPY_HW_ENABLE_USBDEV + #define MICROPY_PY_SYS_PLATFORM "samd" // Extended modules @@ -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/stm32/Makefile b/ports/stm32/Makefile index d1694426d3..50ac48e5a1 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/boards/ARDUINO_GIGA/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk index b927388c55..46fe40c2a6 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk @@ -23,6 +23,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld index d5bd9598f9..793a76b970 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,4 +44,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk index 2c48c17f38..77d547bce0 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk @@ -23,6 +23,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld index fcbfc94d46..6d6ce279f2 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } _cm4_ram_start = ORIGIN(SRAM4); @@ -46,4 +46,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk index cf4d40e5fe..c56c8f005f 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk @@ -23,6 +23,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld index d5bd9598f9..793a76b970 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,4 +44,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld diff --git a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py index 02398e8124..8229004fbb 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) +# Mboot addresses the external SPI flash at this location. +_MBOOT_SPIFLASH_BASE_ADDR = 0x80000000 -_FILESYSTEM_ADDR = const(0x8000_0000 + 1024 * 1024) +# 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) -# 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) + +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 e483ace950..0000000000 --- a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py +++ /dev/null @@ -1,90 +0,0 @@ -# MicroPython driver for SPI flash -# MIT license; Copyright (c) 2022 Damien P. George - -from micropython import const - -_PAGE_SIZE = const(256) # maximum bytes writable in one SPI transfer -_CMD_WRITE = const(0x02) -_CMD_READ = const(0x03) -_CMD_RDSR = const(0x05) -_CMD_WREN = const(0x06) -_CMD_WRITE_32 = const(0x12) -_CMD_READ_32 = const(0x13) -_CMD_SEC_ERASE = const(0x20) -_CMD_SEC_ERASE_32 = const(0x21) -_CMD_JEDEC_ID = const(0x9F) - - -class SPIFlash: - def __init__(self, spi, cs): - self.spi = spi - self.cs = cs - self.id = self._get_id() - # flash chip on Hub No. 6 uses 32-bit addressing - _32_bit = self.id == b"\xef\x40\x19" - self._READ = _CMD_READ_32 if _32_bit else _CMD_READ - self._WRITE = _CMD_WRITE_32 if _32_bit else _CMD_WRITE - self._ERASE = _CMD_SEC_ERASE_32 if _32_bit else _CMD_SEC_ERASE - - def _get_id(self): - self.cs(0) - self.spi.write(bytearray([_CMD_JEDEC_ID])) - buf = self.spi.read(3) - self.cs(1) - return buf - - def _wait_wel1(self): - # wait WEL=1 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if buf[0] & 2: - break - self.cs(1) - - def _wait_wip0(self): - # wait WIP=0 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if not (buf[0] & 1): - break - self.cs(1) - - def _flash_modify(self, cmd, addr, buf): - self.cs(0) - self.spi.write(bytearray([_CMD_WREN])) - self.cs(1) - self._wait_wel1() - self.cs(0) - self.spi.write(bytearray([cmd, addr >> 24, addr >> 16, addr >> 8, addr])) - if buf: - self.spi.write(buf) - self.cs(1) - self._wait_wip0() - - def erase_block(self, addr): - self._flash_modify(self._ERASE, addr, None) - - def readinto(self, addr, buf): - self.cs(0) - self.spi.write(bytearray([self._READ, addr >> 16, addr >> 8, addr])) - self.spi.readinto(buf) - self.cs(1) - - def write(self, addr, buf): - offset = addr & (_PAGE_SIZE - 1) - remain = len(buf) - buf = memoryview(buf) - buf_offset = 0 - while remain: - l = min(_PAGE_SIZE - offset, remain) - self._flash_modify(self._WRITE, addr, buf[buf_offset : buf_offset + l]) - remain -= l - addr += l - buf_offset += l - offset = 0 diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h index e3a204ec96..58e6d0a804 100644 --- a/ports/stm32/irq.h +++ b/ports/stm32/irq.h @@ -174,6 +174,8 @@ static inline void restore_irq_pri(uint32_t state) { #define IRQ_PRI_SPI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0) +#define IRQ_PRI_HSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 10, 0) + // Interrupt priority for non-special timers. #define IRQ_PRI_TIMX NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 13, 0) diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 389f9f5d0f..07053b3293 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -120,6 +120,7 @@ SRC_C += \ ui.c \ vfs_fat.c \ vfs_lfs.c \ + vfs_raw.c \ drivers/bus/softspi.c \ drivers/bus/softqspi.c \ drivers/memory/spiflash.c \ diff --git a/ports/stm32/mboot/README.md b/ports/stm32/mboot/README.md index 73e32fcedc..221e3a7c3a 100644 --- a/ports/stm32/mboot/README.md +++ b/ports/stm32/mboot/README.md @@ -71,13 +71,23 @@ How to use #define MBOOT_FSLOAD (1) and then enable one or more of the following depending on what filesystem - support is required in Mboot (note that the FAT driver is read-only and - quite compact, but littlefs supports both read and write so is rather - large): + support is required in Mboot: #define MBOOT_VFS_FAT (1) #define MBOOT_VFS_LFS1 (1) #define MBOOT_VFS_LFS2 (1) + #define MBOOT_VFS_RAW (1) + + Note that the FAT and LFS2 drivers are read-only and quite compact, but + LFS1 supports both read and write so is rather large. + + The raw filesystem type is enabled by default and is a flat section of + storage containing a single file without any metadata. The raw filesystem + can either be one regoin, or split over two separate, contiguous regions. + The latter is useful for wear levelling: given a chunk of flash, write the + firmware starting at a random block within that chunk and wrap around to + the beginning of the chunk when the end is reached. Then use a split + raw filesystem to inform mboot of this wrapping. 2. Build the board's main application firmware as usual. diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index abc3514ed1..83905eb491 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -39,7 +39,7 @@ #if MBOOT_FSLOAD -#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) +#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2 || MBOOT_VFS_RAW) #error Must enable at least one VFS component #endif @@ -234,28 +234,51 @@ int fsload_process(void) { // End of elements. return -MBOOT_ERRNO_FSLOAD_NO_MOUNT; } + + // Extract element arguments based on the element length: + // - 10 bytes: mount_point fs_type uint32_t uint32_t + // - 14 bytes: mount_point fs_type uint32_t uint32_t uint32_t + // - 18 bytes: mount_point fs_type uint32_t uint32_t uint32_t uint32_t + // - 22 bytes: mount_point fs_type uint64_t uint64_t uint32_t + // - 34 bytes: mount_point fs_type uint64_t uint64_t uint64_t uint64_t mboot_addr_t base_addr; mboot_addr_t byte_len; - uint32_t block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; - if (elem[-1] == 10 || elem[-1] == 14) { + mboot_addr_t arg2 = 0; + mboot_addr_t arg3 = 0; + (void)arg2; + (void)arg3; + uint8_t elem_len = elem[-1]; + if (elem_len == 10 || elem_len == 14 || elem_len == 18) { // 32-bit base and length given, extract them. base_addr = get_le32(&elem[2]); byte_len = get_le32(&elem[6]); - if (elem[-1] == 14) { - // Block size given, extract it. - block_size = get_le32(&elem[10]); + if (elem_len >= 14) { + // Argument 2 given, extract it. + arg2 = get_le32(&elem[10]); + if (elem_len == 18) { + // Argument 3 given, extract it. + arg3 = get_le32(&elem[14]); + } } #if MBOOT_ADDRESS_SPACE_64BIT - } else if (elem[-1] == 22) { - // 64-bit base and length given, and block size, so extract them. + } else if (elem_len == 22 || elem_len == 34) { + // 64-bit base and length given, so extract them. base_addr = get_le64(&elem[2]); byte_len = get_le64(&elem[10]); - block_size = get_le32(&elem[18]); + if (elem_len == 22) { + // 32-bit argument 2 given, extract it. + arg2 = get_le32(&elem[18]); + } else { + // 64-bit argument 2 and 3 given, extract them. + arg2 = get_le64(&elem[18]); + arg3 = get_le64(&elem[26]); + } #endif } else { // Invalid MOUNT element. return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; } + if (elem[0] == mount_point) { int ret; union { @@ -268,27 +291,43 @@ int fsload_process(void) { #if MBOOT_VFS_LFS2 vfs_lfs2_context_t lfs2; #endif + #if MBOOT_VFS_RAW + vfs_raw_context_t raw; + #endif } ctx; const stream_methods_t *methods; #if MBOOT_VFS_FAT if (elem[1] == ELEM_MOUNT_FAT) { - (void)block_size; ret = vfs_fat_mount(&ctx.fat, base_addr, byte_len); methods = &vfs_fat_stream_methods; } else #endif #if MBOOT_VFS_LFS1 if (elem[1] == ELEM_MOUNT_LFS1) { + uint32_t block_size = arg2; + if (block_size == 0) { + block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; + } ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len, block_size); methods = &vfs_lfs1_stream_methods; } else #endif #if MBOOT_VFS_LFS2 if (elem[1] == ELEM_MOUNT_LFS2) { + uint32_t block_size = arg2; + if (block_size == 0) { + block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; + } ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len, block_size); methods = &vfs_lfs2_stream_methods; } else #endif + #if MBOOT_VFS_RAW + if (elem[1] == ELEM_MOUNT_RAW) { + ret = vfs_raw_mount(&ctx.raw, base_addr, byte_len, arg2, arg3); + methods = &vfs_raw_stream_methods; + } else + #endif { // Unknown filesystem type return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index 47ceb19ba5..8578ff4fc9 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -9,6 +9,7 @@ import deflate, machine, stm VFS_FAT = 1 VFS_LFS1 = 2 VFS_LFS2 = 3 +VFS_RAW = 4 # Constants for creating mboot elements. _ELEM_TYPE_END = const(1) @@ -226,28 +227,45 @@ def _create_element(kind, body): def update_app_elements( - filename, fs_base, fs_len, fs_type=VFS_FAT, fs_blocksize=0, status_addr=None, addr_64bit=False + filename, + fs_base, + fs_len, + fs_type=VFS_FAT, + fs_blocksize=0, + status_addr=None, + addr_64bit=False, + *, + fs_base2=0, + fs_len2=0, ): - # Check firmware is of .dfu or .dfu.gz type - try: - with open(filename, "rb") as f: - hdr = deflate.DeflateIO(f, deflate.GZIP).read(6) - except Exception: - with open(filename, "rb") as f: - hdr = f.read(6) - if hdr != b"DfuSe\x01": - print("Firmware must be a .dfu(.gz) file.") - return () + if fs_type != VFS_RAW: + # Check firmware is of .dfu or .dfu.gz type + try: + with open(filename, "rb") as f: + hdr = deflate.DeflateIO(f, deflate.GZIP).read(6) + except Exception: + with open(filename, "rb") as f: + hdr = f.read(6) + if hdr != b"DfuSe\x01": + print("Firmware must be a .dfu(.gz) file.") + return () if fs_type in (VFS_LFS1, VFS_LFS2) and not fs_blocksize: raise Exception("littlefs requires fs_blocksize parameter") mount_point = 1 - mount_encoding = "seg0_base_addr = seg0_base_addr; + ctx->seg0_byte_len = seg0_byte_len; + ctx->seg1_base_addr = seg1_base_addr; + ctx->seg1_byte_len = seg1_byte_len; + return 0; +} + +static int vfs_raw_stream_open(void *stream_in, const char *fname) { + vfs_raw_context_t *stream = stream_in; + (void)fname; + stream->file_pos = 0; + return 0; +} + +static void vfs_raw_stream_close(void *stream_in) { + (void)stream_in; +} + +static int vfs_raw_stream_read(void *stream_in, uint8_t *buf, size_t len) { + vfs_raw_context_t *stream = stream_in; + size_t orig_len = len; + while (len) { + mboot_addr_t addr; + mboot_addr_t remain; + if (stream->file_pos < stream->seg0_byte_len) { + // Reading from segment 0. + mboot_addr_t seg0_pos = stream->file_pos; + addr = stream->seg0_base_addr + seg0_pos; + remain = stream->seg0_byte_len - seg0_pos; + } else { + // Reading from segment 1. + mboot_addr_t seg1_pos = stream->file_pos - stream->seg0_byte_len; + addr = stream->seg1_base_addr + seg1_pos; + remain = stream->seg1_byte_len - seg1_pos; + if (!remain) { + // At the end of segment 1. + break; + } + } + size_t l = MIN(len, remain); + hw_read(addr, l, buf); + stream->file_pos += l; + buf += l; + len -= l; + } + return orig_len - len; +} + +const stream_methods_t vfs_raw_stream_methods = { + vfs_raw_stream_open, + vfs_raw_stream_close, + vfs_raw_stream_read, +}; + +#endif // MBOOT_FSLOAD && MBOOT_VFS_RAW diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 300ad086bf..9e1e24cf23 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -111,6 +111,7 @@ #ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/stm32/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/stm32/mpmetalport.c b/ports/stm32/mpmetalport.c new file mode 100644 index 0000000000..72f5537ce5 --- /dev/null +++ b/ports/stm32/mpmetalport.c @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * libmetal stm32 port. + */ + +#include "py/mperrno.h" +#include "py/mphal.h" + +#include "mpu.h" + +#include "metal/sys.h" +#include "metal/utilities.h" +#include "metal/device.h" + +struct metal_state _metal; +static mp_sched_node_t rproc_notify_node; + +int metal_sys_init(const struct metal_init_params *params) { + metal_unused(params); + + // Clear HSEM pending IRQ. + HSEM_COMMON->ICR |= (uint32_t)__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID); + HAL_NVIC_ClearPendingIRQ(HSEM1_IRQn); + + // Enable and configure HSEM. + __HAL_RCC_HSEM_CLK_ENABLE(); + NVIC_SetPriority(HSEM1_IRQn, IRQ_PRI_HSEM); + HAL_NVIC_EnableIRQ(HSEM1_IRQn); + HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); + + #ifndef VIRTIO_USE_DCACHE + // If cache management is not enabled, configure the MPU to disable caching + // for the entire shared memory region. + uint32_t irq_state = mpu_config_start(); + mpu_config_region(MPU_REGION_OPENAMP, METAL_MPU_REGION_BASE, MPU_CONFIG_SHARED_UNCACHED(METAL_MPU_REGION_SIZE)); + mpu_config_end(irq_state); + #endif + + metal_bus_register(&metal_generic_bus); + return 0; +} + +void metal_sys_finish(void) { + HAL_NVIC_DisableIRQ(HSEM1_IRQn); + HAL_HSEM_DeactivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); + __HAL_RCC_HSEM_CLK_DISABLE(); + metal_bus_unregister(&metal_generic_bus); +} + +unsigned int sys_irq_save_disable(void) { + return disable_irq(); +} + +void sys_irq_restore_enable(unsigned int state) { + enable_irq(state); +} + +void *metal_machine_io_mem_map(void *va, metal_phys_addr_t pa, + size_t size, unsigned int flags) { + metal_unused(pa); + metal_unused(size); + metal_unused(flags); + return va; +} + +void metal_machine_cache_flush(void *addr, unsigned int len) { + SCB_CleanDCache_by_Addr(addr, len); +} + +void metal_machine_cache_invalidate(void *addr, unsigned int len) { + SCB_InvalidateDCache_by_Addr(addr, len); +} + +int metal_rproc_notify(void *priv, uint32_t id) { + HAL_HSEM_FastTake(METAL_HSEM_REMOTE_ID); + HAL_HSEM_Release(METAL_HSEM_REMOTE_ID, 0); + return 0; +} + +void HSEM1_IRQHandler(void) { + HAL_HSEM_IRQHandler(); + mp_sched_schedule_node(&rproc_notify_node, openamp_remoteproc_notified); + HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); +} diff --git a/ports/stm32/mpmetalport.h b/ports/stm32/mpmetalport.h new file mode 100644 index 0000000000..53f329d15b --- /dev/null +++ b/ports/stm32/mpmetalport.h @@ -0,0 +1,76 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * libmetal stm32 port. + */ +#ifndef MICROPY_INCLUDED_STM32_MPMETALPORT_H +#define MICROPY_INCLUDED_STM32_MPMETALPORT_H + +#include +#include "py/mphal.h" +#include "py/runtime.h" + +#define METAL_HAVE_STDATOMIC_H 0 +#define METAL_HAVE_FUTEX_H 0 + +#define METAL_MAX_DEVICE_REGIONS 2 + +#define METAL_HSEM_REMOTE_ID 0 +#define METAL_HSEM_MASTER_ID 1 + +// Set to 1 to enable log output. +#define METAL_LOG_HANDLER_ENABLE 0 + +#define metal_cpu_yield() + +// Shared memory config +#define METAL_SHM_NAME "OPENAMP_SHM" +// Note 1K must be reserved at the start of the openamp +// shared memory region, for the shared resource table. +#define METAL_RSC_ADDR ((void *)_openamp_shm_region_start) +#define METAL_RSC_SIZE (1024) + +#define METAL_SHM_ADDR ((metal_phys_addr_t)(_openamp_shm_region_start + METAL_RSC_SIZE)) +#define METAL_SHM_SIZE ((size_t)(_openamp_shm_region_end - _openamp_shm_region_start - METAL_RSC_SIZE)) + +#define METAL_MPU_REGION_BASE ((uint32_t)_openamp_shm_region_start) +#define METAL_MPU_REGION_SIZE (MPU_REGION_SIZE_64KB) + +extern const char _openamp_shm_region_start[]; +extern const char _openamp_shm_region_end[]; + +int metal_rproc_notify(void *priv, uint32_t id); +extern void openamp_remoteproc_notified(mp_sched_node_t *node); + +static inline int __metal_sleep_usec(unsigned int usec) { + mp_hal_delay_us(usec); + return 0; +} + +static inline void metal_generic_default_poll(void) { + MICROPY_EVENT_POLL_HOOK +} + +#endif // MICROPY_INCLUDED_STM32_METAL_PORT_H diff --git a/ports/stm32/mpremoteprocport.c b/ports/stm32/mpremoteprocport.c new file mode 100644 index 0000000000..56dff43da4 --- /dev/null +++ b/ports/stm32/mpremoteprocport.c @@ -0,0 +1,153 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * modremoteproc stm32 port. + */ + +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" +#include "metal/sys.h" +#include "metal/device.h" +#include "metal/utilities.h" +#include "extmod/modopenamp_remoteproc.h" + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +struct remoteproc *mp_openamp_remoteproc_init(struct remoteproc *rproc, + const struct remoteproc_ops *ops, void *arg) { + DEBUG_printf("rproc_init()\n"); + + rproc->ops = ops; + rproc->state = RPROC_OFFLINE; + // Allocate the image store and save it in private data. + rproc->priv = mp_openamp_remoteproc_store_alloc(); + return rproc; +} + +void *mp_openamp_remoteproc_mmap(struct remoteproc *rproc, metal_phys_addr_t *pa, + metal_phys_addr_t *da, size_t size, unsigned int attribute, + struct metal_io_region **io) { + DEBUG_printf("rproc_mmap(): pa 0x%p da 0x%p io 0x%p size %u\n", *pa, *da, *io, size); + + struct remoteproc_mem *mem; + metal_phys_addr_t lpa = *pa; + metal_phys_addr_t lda = *da; + + if (lda == METAL_BAD_PHYS) { + return NULL; + } + + if (lpa == METAL_BAD_PHYS) { + lpa = lda; + } + + // Currently this port doesn't support loading firmware to flash, + // only SD/SRAM images are supported. Check of load address is in + // the flash region, and if so return NULL. + if (lda >= FLASH_BASE && lda < FLASH_END) { + return NULL; + } + + mem = metal_allocate_memory(sizeof(*mem)); + if (!mem) { + return NULL; + } + + *io = metal_allocate_memory(sizeof(struct metal_io_region)); + if (!*io) { + metal_free_memory(mem); + return NULL; + } + + remoteproc_init_mem(mem, NULL, lpa, lda, size, *io); + + metal_io_init(*io, (void *)mem->da, &mem->pa, size, + sizeof(metal_phys_addr_t) << 3, attribute, NULL); + + remoteproc_add_mem(rproc, mem); + *pa = lpa; + *da = lda; + return metal_io_phys_to_virt(*io, mem->pa); +} + +int mp_openamp_remoteproc_start(struct remoteproc *rproc) { + DEBUG_printf("rproc_start()\n"); + if ((RCC->GCR & RCC_GCR_BOOT_C2) || (FLASH->OPTSR_CUR & FLASH_OPTSR_BCM4)) { + // The CM4 core has already been started manually, or auto-boot is enabled + // via the option bytes, in either case the core can't be restarted. + DEBUG_printf("rproc_start(): CM4 core is already booted.\n"); + return -1; + } + + // Flush M7 cache. + struct metal_list *node; + metal_list_for_each(&rproc->mems, node) { + struct remoteproc_mem *mem; + mem = metal_container_of(node, struct remoteproc_mem, node); + SCB_CleanDCache_by_Addr((uint32_t *)mem->pa, mem->size); + } + + HAL_SYSCFG_CM4BootAddConfig(SYSCFG_BOOT_ADDR0, (uint32_t)rproc->bootaddr); + HAL_RCCEx_EnableBootCore(RCC_BOOT_C2); + return 0; +} + +int mp_openamp_remoteproc_stop(struct remoteproc *rproc) { + DEBUG_printf("rproc_stop()\n"); + if (rproc->state == RPROC_RUNNING) { + // There's no straightforward way to reset or shut down + // the remote processor, so a full system reset is needed. + NVIC_SystemReset(); + } + return 0; +} + +int mp_openamp_remoteproc_config(struct remoteproc *rproc, void *data) { + DEBUG_printf("rproc_config()\n"); + (void)rproc; + return 0; +} + +void mp_openamp_remoteproc_remove(struct remoteproc *rproc) { + DEBUG_printf("rproc_remove()\n"); + (void)rproc; +} + +int mp_openamp_remoteproc_shutdown(struct remoteproc *rproc) { + DEBUG_printf("rproc_shutdown()\n"); + if (rproc->state == RPROC_RUNNING) { + // There's no straightforward way to reset or shut down + // the remote processor, so a full system reset is needed. + NVIC_SystemReset(); + } + return 0; +} diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index 64880a85db..5ef1466184 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -36,6 +36,7 @@ #define MPU_REGION_QSPI3 (MPU_REGION_NUMBER3) #define MPU_REGION_SDRAM1 (MPU_REGION_NUMBER4) #define MPU_REGION_SDRAM2 (MPU_REGION_NUMBER5) +#define MPU_REGION_OPENAMP (MPU_REGION_NUMBER15) // Only relevant on CPUs with D-Cache, must be higher priority than SDRAM #define MPU_REGION_DMA_UNCACHED_1 (MPU_REGION_NUMBER6) @@ -94,6 +95,18 @@ | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos \ ) +#define MPU_CONFIG_SHARED_UNCACHED(size) ( \ + MPU_INSTRUCTION_ACCESS_DISABLE << MPU_RASR_XN_Pos \ + | MPU_REGION_FULL_ACCESS << MPU_RASR_AP_Pos \ + | MPU_TEX_LEVEL1 << MPU_RASR_TEX_Pos \ + | MPU_ACCESS_SHAREABLE << MPU_RASR_S_Pos \ + | MPU_ACCESS_NOT_CACHEABLE << MPU_RASR_C_Pos \ + | MPU_ACCESS_NOT_BUFFERABLE << MPU_RASR_B_Pos \ + | 0x00 << MPU_RASR_SRD_Pos \ + | (size) << MPU_RASR_SIZE_Pos \ + | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos \ + ) + static inline void mpu_init(void) { MPU->CTRL = MPU_PRIVILEGED_DEFAULT | MPU_CTRL_ENABLE_Msk; SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; diff --git a/ports/stm32/stm32.mk b/ports/stm32/stm32.mk index b9f70db73e..718fa8cf06 100644 --- a/ports/stm32/stm32.mk +++ b/ports/stm32/stm32.mk @@ -44,14 +44,14 @@ ifneq ($(BUILDING_MBOOT),1) SUPPORTS_HARDWARE_FP_SINGLE = 0 SUPPORTS_HARDWARE_FP_DOUBLE = 0 ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx STM32H747xx STM32H750xx STM32H7A3xx STM32H7A3xxQ STM32H7B3xx STM32H7B3xxQ)) -CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard +CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 SUPPORTS_HARDWARE_FP_DOUBLE = 1 else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 g0 l0 l1 wl)) CFLAGS_CORTEX_M += -msoft-float else -CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard +CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 endif endif diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 981babca9f..2e34055bf7 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -41,6 +41,11 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) #endif +// Don't use native _Float16 because it increases code size by a lot. +#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16 +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0) +#endif + // Enable arbitrary precision long-int by default. #ifndef MICROPY_LONGINT_IMPL #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index dee5568c62..55e44c6f5c 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -117,6 +117,8 @@ #define MICROPY_PY_SYS_STDFILES (1) #define MICROPY_PY_SYS_EXC_INFO (1) #define MICROPY_PY_COLLECTIONS_DEQUE (1) +#define MICROPY_PY_COLLECTIONS_DEQUE_ITER (1) +#define MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR (1) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) #ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) diff --git a/py/asmarm.c b/py/asmarm.c index cd346949eb..6006490701 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -77,6 +77,11 @@ static uint asm_arm_op_mvn_imm(uint rd, uint imm) { return 0x3e00000 | (rd << 12) | imm; } +static uint asm_arm_op_mvn_reg(uint rd, uint rm) { + // mvn rd, rm + return 0x1e00000 | (rd << 12) | rm; +} + static uint asm_arm_op_add_imm(uint rd, uint rn, uint imm) { // add rd, rn, #imm return 0x2800000 | (rn << 16) | (rd << 12) | (imm & 0xFF); @@ -97,6 +102,11 @@ static uint asm_arm_op_sub_reg(uint rd, uint rn, uint rm) { return 0x0400000 | (rn << 16) | (rd << 12) | rm; } +static uint asm_arm_op_rsb_imm(uint rd, uint rn, uint imm) { + // rsb rd, rn, #imm + return 0x2600000 | (rn << 16) | (rd << 12) | (imm & 0xFF); +} + static uint asm_arm_op_mul_reg(uint rd, uint rm, uint rs) { // mul rd, rm, rs assert(rd != rm); @@ -228,11 +238,23 @@ void asm_arm_setcc_reg(asm_arm_t *as, uint rd, uint cond) { emit(as, asm_arm_op_mov_imm(rd, 0) | (cond ^ (1 << 28))); // mov!COND rd, #0 } +void asm_arm_mvn_reg_reg(asm_arm_t *as, uint rd, uint rm) { + // mvn rd, rm + // computes: rd := ~rm + emit_al(as, asm_arm_op_mvn_reg(rd, rm)); +} + void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // add rd, rn, rm emit_al(as, asm_arm_op_add_reg(rd, rn, rm)); } +void asm_arm_rsb_reg_reg_imm(asm_arm_t *as, uint rd, uint rn, uint imm) { + // rsb rd, rn, #imm + // computes: rd := #imm - rn + emit_al(as, asm_arm_op_rsb_imm(rd, rn, imm)); +} + void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // sub rd, rn, rm emit_al(as, asm_arm_op_sub_reg(rd, rn, rm)); diff --git a/py/asmarm.h b/py/asmarm.h index 81c3f7b1cf..4a4253aef6 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -94,8 +94,10 @@ void asm_arm_cmp_reg_i8(asm_arm_t *as, uint rd, int imm); void asm_arm_cmp_reg_reg(asm_arm_t *as, uint rd, uint rn); // arithmetic +void asm_arm_mvn_reg_reg(asm_arm_t *as, uint rd, uint rm); void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); +void asm_arm_rsb_reg_reg_imm(asm_arm_t *as, uint rd, uint rn, uint imm); void asm_arm_mul_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_and_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_eor_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); @@ -188,6 +190,8 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_arm_mov_reg_local_addr((as), (reg_dest), (local_num)) #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_arm_mov_reg_pcrel((as), (reg_dest), (label)) +#define ASM_NOT_REG(as, reg_dest) asm_arm_mvn_reg_reg((as), (reg_dest), (reg_dest)) +#define ASM_NEG_REG(as, reg_dest) asm_arm_rsb_reg_reg_imm((as), (reg_dest), (reg_dest), 0) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_arm_lsl_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_arm_lsr_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_arm_asr_reg_reg((as), (reg_dest), (reg_shift)) diff --git a/py/asmthumb.h b/py/asmthumb.h index 2829ac4c7e..a9e68d7adb 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -406,6 +406,8 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_thumb_mov_reg_local_addr((as), (reg_dest), (local_num)) #define ASM_MOV_REG_PCREL(as, rlo_dest, label) asm_thumb_mov_reg_pcrel((as), (rlo_dest), (label)) +#define ASM_NOT_REG(as, reg_dest) asm_thumb_mvn_rlo_rlo((as), (reg_dest), (reg_dest)) +#define ASM_NEG_REG(as, reg_dest) asm_thumb_neg_rlo_rlo((as), (reg_dest), (reg_dest)) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSL, (reg_dest), (reg_shift)) #define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSR, (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_ASR, (reg_dest), (reg_shift)) diff --git a/py/asmx64.c b/py/asmx64.c index abddc16269..d9f33cfb2a 100644 --- a/py/asmx64.c +++ b/py/asmx64.c @@ -54,6 +54,8 @@ #define OPCODE_MOVZX_RM8_TO_R64 (0xb6) /* 0x0f 0xb6/r */ #define OPCODE_MOVZX_RM16_TO_R64 (0xb7) /* 0x0f 0xb7/r */ #define OPCODE_LEA_MEM_TO_R64 (0x8d) /* /r */ +#define OPCODE_NOT_RM64 (0xf7) /* /2 */ +#define OPCODE_NEG_RM64 (0xf7) /* /3 */ #define OPCODE_AND_R64_TO_RM64 (0x21) /* /r */ #define OPCODE_OR_R64_TO_RM64 (0x09) /* /r */ #define OPCODE_XOR_R64_TO_RM64 (0x31) /* /r */ @@ -362,6 +364,14 @@ void asm_x64_mov_i64_to_r64_optimised(asm_x64_t *as, int64_t src_i64, int dest_r } } +void asm_x64_not_r64(asm_x64_t *as, int dest_r64) { + asm_x64_generic_r64_r64(as, dest_r64, 2, OPCODE_NOT_RM64); +} + +void asm_x64_neg_r64(asm_x64_t *as, int dest_r64) { + asm_x64_generic_r64_r64(as, dest_r64, 3, OPCODE_NEG_RM64); +} + void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_AND_R64_TO_RM64); } diff --git a/py/asmx64.h b/py/asmx64.h index a8efc2bf60..c63e31797e 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -97,6 +97,8 @@ void asm_x64_mov_mem8_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int des void asm_x64_mov_mem16_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64); void asm_x64_mov_mem32_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64); void asm_x64_mov_mem64_to_r64(asm_x64_t *as, int src_r64, int src_disp, int dest_r64); +void asm_x64_not_r64(asm_x64_t *as, int dest_r64); +void asm_x64_neg_r64(asm_x64_t *as, int dest_r64); void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_or_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_xor_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); @@ -191,6 +193,8 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_x64_mov_local_addr_to_r64((as), (local_num), (reg_dest)) #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x64_mov_reg_pcrel((as), (reg_dest), (label)) +#define ASM_NOT_REG(as, reg) asm_x64_not_r64((as), (reg)) +#define ASM_NEG_REG(as, reg) asm_x64_neg_r64((as), (reg)) #define ASM_LSL_REG(as, reg) asm_x64_shl_r64_cl((as), (reg)) #define ASM_LSR_REG(as, reg) asm_x64_shr_r64_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x64_sar_r64_cl((as), (reg)) diff --git a/py/asmx86.c b/py/asmx86.c index 94e0213d65..4acac1b46a 100644 --- a/py/asmx86.c +++ b/py/asmx86.c @@ -54,6 +54,8 @@ #define OPCODE_MOVZX_RM8_TO_R32 (0xb6) /* 0x0f 0xb6/r */ #define OPCODE_MOVZX_RM16_TO_R32 (0xb7) /* 0x0f 0xb7/r */ #define OPCODE_LEA_MEM_TO_R32 (0x8d) /* /r */ +#define OPCODE_NOT_RM32 (0xf7) /* /2 */ +#define OPCODE_NEG_RM32 (0xf7) /* /3 */ #define OPCODE_AND_R32_TO_RM32 (0x21) /* /r */ #define OPCODE_OR_R32_TO_RM32 (0x09) /* /r */ #define OPCODE_XOR_R32_TO_RM32 (0x31) /* /r */ @@ -244,6 +246,14 @@ size_t asm_x86_mov_i32_to_r32(asm_x86_t *as, int32_t src_i32, int dest_r32) { return loc; } +void asm_x86_not_r32(asm_x86_t *as, int dest_r32) { + asm_x86_generic_r32_r32(as, dest_r32, 2, OPCODE_NOT_RM32); +} + +void asm_x86_neg_r32(asm_x86_t *as, int dest_r32) { + asm_x86_generic_r32_r32(as, dest_r32, 3, OPCODE_NEG_RM32); +} + void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_AND_R32_TO_RM32); } diff --git a/py/asmx86.h b/py/asmx86.h index ac98643a6a..027d44151e 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -92,6 +92,8 @@ void asm_x86_mov_r32_to_mem32(asm_x86_t *as, int src_r32, int dest_r32, int dest void asm_x86_mov_mem8_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32); void asm_x86_mov_mem16_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32); void asm_x86_mov_mem32_to_r32(asm_x86_t *as, int src_r32, int src_disp, int dest_r32); +void asm_x86_not_r32(asm_x86_t *as, int dest_r32); +void asm_x86_neg_r32(asm_x86_t *as, int dest_r32); void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_or_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_xor_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); @@ -186,6 +188,8 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_x86_mov_local_addr_to_r32((as), (local_num), (reg_dest)) #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x86_mov_reg_pcrel((as), (reg_dest), (label)) +#define ASM_NOT_REG(as, reg) asm_x86_not_r32((as), (reg)) +#define ASM_NEG_REG(as, reg) asm_x86_neg_r32((as), (reg)) #define ASM_LSL_REG(as, reg) asm_x86_shl_r32_cl((as), (reg)) #define ASM_LSR_REG(as, reg) asm_x86_shr_r32_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x86_sar_r32_cl((as), (reg)) diff --git a/py/asmxtensa.c b/py/asmxtensa.c index 84018402f6..0fbe351dcf 100644 --- a/py/asmxtensa.c +++ b/py/asmxtensa.c @@ -185,7 +185,9 @@ size_t asm_xtensa_mov_reg_i32(asm_xtensa_t *as, uint reg_dest, uint32_t i32) { } void asm_xtensa_mov_reg_i32_optimised(asm_xtensa_t *as, uint reg_dest, uint32_t i32) { - if (SIGNED_FIT12(i32)) { + if (-32 <= (int)i32 && (int)i32 <= 95) { + asm_xtensa_op_movi_n(as, reg_dest, i32); + } else if (SIGNED_FIT12(i32)) { asm_xtensa_op_movi(as, reg_dest, i32); } else { asm_xtensa_mov_reg_i32(as, reg_dest, i32); diff --git a/py/asmxtensa.h b/py/asmxtensa.h index 5bd6426a14..f226624a82 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 4c8b6ffcdc..7c01cfa1c8 100644 --- a/py/binary.c +++ b/py/binary.c @@ -74,11 +74,14 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { case 'S': size = sizeof(void *); break; + case 'e': + size = 2; + break; case 'f': - size = sizeof(float); + size = 4; break; case 'd': - size = sizeof(double); + size = 8; break; } break; @@ -122,6 +125,10 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { align = alignof(void *); size = sizeof(void *); break; + case 'e': + align = 2; + size = 2; + break; case 'f': align = alignof(float); size = sizeof(float); @@ -144,6 +151,99 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { return size; } +#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_USE_NATIVE_FLT16 + +static inline float mp_decode_half_float(uint16_t hf) { + union { + uint16_t i; + _Float16 f; + } fpu = { .i = hf }; + return fpu.f; +} + +static inline uint16_t mp_encode_half_float(float x) { + union { + uint16_t i; + _Float16 f; + } fp_sp = { .f = (_Float16)x }; + return fp_sp.i; +} + +#elif MICROPY_PY_BUILTINS_FLOAT + +static float mp_decode_half_float(uint16_t hf) { + union { + uint32_t i; + float f; + } fpu; + + uint16_t m = hf & 0x3ff; + int e = (hf >> 10) & 0x1f; + if (e == 0x1f) { + // Half-float is infinity. + e = 0xff; + } else if (e) { + // Half-float is normal. + e += 127 - 15; + } else if (m) { + // Half-float is subnormal, make it normal. + e = 127 - 15; + while (!(m & 0x400)) { + m <<= 1; + --e; + } + m -= 0x400; + ++e; + } + + fpu.i = ((hf & 0x8000) << 16) | (e << 23) | (m << 13); + return fpu.f; +} + +static uint16_t mp_encode_half_float(float x) { + union { + uint32_t i; + float f; + } fpu = { .f = x }; + + uint16_t m = (fpu.i >> 13) & 0x3ff; + if (fpu.i & (1 << 12)) { + // Round up. + ++m; + } + int e = (fpu.i >> 23) & 0xff; + + if (e == 0xff) { + // Infinity. + e = 0x1f; + } else if (e != 0) { + e -= 127 - 15; + if (e < 0) { + // Underflow: denormalized, or zero. + if (e >= -11) { + m = (m | 0x400) >> -e; + if (m & 1) { + m = (m >> 1) + 1; + } else { + m >>= 1; + } + } else { + m = 0; + } + e = 0; + } else if (e > 0x3f) { + // Overflow: infinity. + e = 0x1f; + m = 0; + } + } + + uint16_t bits = ((fpu.i >> 16) & 0x8000) | (e << 10) | m; + return bits; +} + +#endif + mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) { mp_int_t val = 0; switch (typecode) { @@ -240,6 +340,8 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte * const char *s_val = (const char *)(uintptr_t)(mp_uint_t)val; return mp_obj_new_str(s_val, strlen(s_val)); #if MICROPY_PY_BUILTINS_FLOAT + } else if (val_type == 'e') { + return mp_obj_new_float_from_f(mp_decode_half_float(val)); } else if (val_type == 'f') { union { uint32_t i; @@ -309,6 +411,9 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p val = (mp_uint_t)val_in; break; #if MICROPY_PY_BUILTINS_FLOAT + case 'e': + val = mp_encode_half_float(mp_obj_get_float_to_f(val_in)); + break; case 'f': { union { uint32_t i; diff --git a/py/emitnative.c b/py/emitnative.c index f80461dd42..0b84a2ec8a 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -2259,15 +2259,38 @@ static void emit_native_pop_except_jump(emit_t *emit, mp_uint_t label, bool with } static void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) { - vtype_kind_t vtype; - emit_pre_pop_reg(emit, &vtype, REG_ARG_2); - if (vtype == VTYPE_PYOBJ) { + vtype_kind_t vtype = peek_vtype(emit, 0); + if (vtype == VTYPE_INT || vtype == VTYPE_UINT) { + if (op == MP_UNARY_OP_POSITIVE) { + // No-operation, just leave the argument on the stack. + } else if (op == MP_UNARY_OP_NEGATIVE) { + int reg = REG_RET; + emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg); + ASM_NEG_REG(emit->as, reg); + emit_post_push_reg(emit, vtype, reg); + } else if (op == MP_UNARY_OP_INVERT) { + #ifdef ASM_NOT_REG + int reg = REG_RET; + emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg); + ASM_NOT_REG(emit->as, reg); + #else + int reg = REG_RET; + emit_pre_pop_reg_flexible(emit, &vtype, ®, REG_ARG_1, reg); + ASM_MOV_REG_IMM(emit->as, REG_ARG_1, -1); + ASM_XOR_REG_REG(emit->as, reg, REG_ARG_1); + #endif + emit_post_push_reg(emit, vtype, reg); + } else { + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, + MP_ERROR_TEXT("'not' not implemented"), mp_binary_op_method_name[op]); + } + } else if (vtype == VTYPE_PYOBJ) { + emit_pre_pop_reg(emit, &vtype, REG_ARG_2); emit_call_with_imm_arg(emit, MP_F_UNARY_OP, op, REG_ARG_1); emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET); } else { - adjust_stack(emit, 1); EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - MP_ERROR_TEXT("unary op %q not implemented"), mp_unary_op_method_name[op]); + MP_ERROR_TEXT("can't do unary op of '%q'"), vtype_to_qstr(vtype)); } } diff --git a/py/mpconfig.h b/py/mpconfig.h index 90f8e592bf..d9cff930d1 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -830,6 +830,15 @@ typedef double mp_float_t; #define MICROPY_PY_BUILTINS_COMPLEX (MICROPY_PY_BUILTINS_FLOAT) #endif +// Whether to use the native _Float16 for 16-bit float support +#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16 +#ifdef __FLT16_MAX__ +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (1) +#else +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0) +#endif +#endif + // Whether to provide a high-quality hash for float and complex numbers. // Otherwise the default is a very simple but correct hashing function. #ifndef MICROPY_FLOAT_HIGH_QUALITY_HASH @@ -1303,6 +1312,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 +1711,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 +1726,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/objdeque.c b/py/objdeque.c index 68e1621793..583537017f 100644 --- a/py/objdeque.c +++ b/py/objdeque.c @@ -25,13 +25,11 @@ */ #include // for ssize_t -#include - -#include "py/mpconfig.h" -#if MICROPY_PY_COLLECTIONS_DEQUE #include "py/runtime.h" +#if MICROPY_PY_COLLECTIONS_DEQUE + typedef struct _mp_obj_deque_t { mp_obj_base_t base; size_t alloc; @@ -42,15 +40,15 @@ typedef struct _mp_obj_deque_t { #define FLAG_CHECK_OVERFLOW 1 } mp_obj_deque_t; +static mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg); +static mp_obj_t mp_obj_deque_extend(mp_obj_t self_in, mp_obj_t arg_in); +#if MICROPY_PY_COLLECTIONS_DEQUE_ITER +static mp_obj_t mp_obj_new_deque_it(mp_obj_t deque, mp_obj_iter_buf_t *iter_buf); +#endif + static mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 2, 3, false); - /* Initialization from existing sequence is not supported, so an empty - tuple must be passed as such. */ - if (args[0] != mp_const_empty_tuple) { - mp_raise_ValueError(NULL); - } - // Protect against -1 leading to zero-length allocation and bad array access mp_int_t maxlen = mp_obj_get_int(args[1]); if (maxlen < 0) { @@ -66,21 +64,27 @@ static mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t o->flags = mp_obj_get_int(args[2]); } + mp_obj_deque_extend(MP_OBJ_FROM_PTR(o), args[0]); + return MP_OBJ_FROM_PTR(o); } +static size_t deque_len(mp_obj_deque_t *self) { + ssize_t len = self->i_put - self->i_get; + if (len < 0) { + len += self->alloc; + } + return len; +} + static mp_obj_t deque_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->i_get != self->i_put); - case MP_UNARY_OP_LEN: { - ssize_t len = self->i_put - self->i_get; - if (len < 0) { - len += self->alloc; - } - return MP_OBJ_NEW_SMALL_INT(len); - } + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(deque_len(self)); + #if MICROPY_PY_SYS_GETSIZEOF case MP_UNARY_OP_SIZEOF: { size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc; @@ -117,6 +121,45 @@ static mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg) { } static MP_DEFINE_CONST_FUN_OBJ_2(deque_append_obj, mp_obj_deque_append); +static mp_obj_t mp_obj_deque_appendleft(mp_obj_t self_in, mp_obj_t arg) { + mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); + + size_t new_i_get = self->i_get - 1; + if (self->i_get == 0) { + new_i_get = self->alloc - 1; + } + + if (self->flags & FLAG_CHECK_OVERFLOW && new_i_get == self->i_put) { + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("full")); + } + + self->i_get = new_i_get; + self->items[self->i_get] = arg; + + // overwriting first element in deque + if (self->i_put == new_i_get) { + if (self->i_put == 0) { + self->i_put = self->alloc - 1; + } else { + self->i_put--; + } + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(deque_appendleft_obj, mp_obj_deque_appendleft); + +static mp_obj_t mp_obj_deque_extend(mp_obj_t self_in, mp_obj_t arg_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iter = mp_getiter(arg_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + mp_obj_deque_append(self_in, item); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(deque_extend_obj, mp_obj_deque_extend); + static mp_obj_t deque_popleft(mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); @@ -135,6 +178,51 @@ static mp_obj_t deque_popleft(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(deque_popleft_obj, deque_popleft); +static mp_obj_t deque_pop(mp_obj_t self_in) { + mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->i_get == self->i_put) { + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty")); + } + + if (self->i_put == 0) { + self->i_put = self->alloc - 1; + } else { + self->i_put--; + } + + mp_obj_t ret = self->items[self->i_put]; + self->items[self->i_put] = MP_OBJ_NULL; + + return ret; +} +static MP_DEFINE_CONST_FUN_OBJ_1(deque_pop_obj, deque_pop); + +#if MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR +static mp_obj_t deque_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete not supported, fall back to mp_obj_subscr() error message + return MP_OBJ_NULL; + } + mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); + + size_t offset = mp_get_index(self->base.type, deque_len(self), index, false); + size_t index_val = self->i_get + offset; + if (index_val > self->alloc) { + index_val -= self->alloc; + } + + if (value == MP_OBJ_SENTINEL) { + // load + return self->items[index_val]; + } else { + // store into deque + self->items[index_val] = value; + return mp_const_none; + } +} +#endif + #if 0 static mp_obj_t deque_clear(mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); @@ -147,21 +235,80 @@ static MP_DEFINE_CONST_FUN_OBJ_1(deque_clear_obj, deque_clear); static const mp_rom_map_elem_t deque_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&deque_append_obj) }, + { MP_ROM_QSTR(MP_QSTR_appendleft), MP_ROM_PTR(&deque_appendleft_obj) }, + { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&deque_extend_obj) }, #if 0 { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&deque_clear_obj) }, #endif + { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&deque_pop_obj) }, { MP_ROM_QSTR(MP_QSTR_popleft), MP_ROM_PTR(&deque_popleft_obj) }, }; static MP_DEFINE_CONST_DICT(deque_locals_dict, deque_locals_dict_table); +#if MICROPY_PY_COLLECTIONS_DEQUE_ITER +#define DEQUE_TYPE_FLAGS MP_TYPE_FLAG_ITER_IS_GETITER +#define DEQUE_TYPE_ITER iter, mp_obj_new_deque_it, +#else +#define DEQUE_TYPE_FLAGS MP_TYPE_FLAG_NONE +#define DEQUE_TYPE_ITER +#endif + +#if MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR +#define DEQUE_TYPE_SUBSCR subscr, deque_subscr, +#else +#define DEQUE_TYPE_SUBSCR +#endif + MP_DEFINE_CONST_OBJ_TYPE( mp_type_deque, MP_QSTR_deque, - MP_TYPE_FLAG_NONE, + MP_TYPE_FLAG_ITER_IS_GETITER, make_new, deque_make_new, unary_op, deque_unary_op, + DEQUE_TYPE_SUBSCR + DEQUE_TYPE_ITER locals_dict, &deque_locals_dict ); +/******************************************************************************/ +/* deque iterator */ + +#if MICROPY_PY_COLLECTIONS_DEQUE_ITER + +typedef struct _mp_obj_deque_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t deque; + size_t cur; +} mp_obj_deque_it_t; + +static mp_obj_t deque_it_iternext(mp_obj_t self_in) { + mp_obj_deque_it_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_deque_t *deque = MP_OBJ_TO_PTR(self->deque); + if (self->cur != deque->i_put) { + mp_obj_t o_out = deque->items[self->cur]; + if (++self->cur == deque->alloc) { + self->cur = 0; + } + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +static mp_obj_t mp_obj_new_deque_it(mp_obj_t deque, mp_obj_iter_buf_t *iter_buf) { + mp_obj_deque_t *deque_ = MP_OBJ_TO_PTR(deque); + size_t i_get = deque_->i_get; + assert(sizeof(mp_obj_deque_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_deque_it_t *o = (mp_obj_deque_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = deque_it_iternext; + o->deque = deque; + o->cur = i_get; + return MP_OBJ_FROM_PTR(o); +} + +#endif + #endif // MICROPY_PY_COLLECTIONS_DEQUE diff --git a/py/objstr.h b/py/objstr.h index 72fe1cfef0..028fc9597f 100644 --- a/py/objstr.h +++ b/py/objstr.h @@ -100,6 +100,8 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s mp_obj_t index, bool is_slice); const byte *find_subbytes(const byte *haystack, size_t hlen, const byte *needle, size_t nlen, int direction); +#define MP_DEFINE_BYTES_OBJ(obj_name, target, len) mp_obj_str_t obj_name = {{&mp_type_bytes}, 0, (len), (const byte *)(target)} + mp_obj_t mp_obj_bytes_hex(size_t n_args, const mp_obj_t *args, const mp_obj_type_t *type); mp_obj_t mp_obj_bytes_fromhex(mp_obj_t type_in, mp_obj_t data); diff --git a/py/runtime.c b/py/runtime.c index f7e0abdb46..1836f5d92a 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -171,6 +171,10 @@ void mp_init(void) { MP_STATE_VM(bluetooth) = MP_OBJ_NULL; #endif + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + MP_STATE_VM(usbd) = MP_OBJ_NULL; + #endif + #if MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_VM(gil_mutex)); #endif diff --git a/py/stream.c b/py/stream.c index aa905ca8cc..d7a8881e1a 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 e6e6f283df..7c4d38afa9 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/shared/tinyusb/mp_usbd.c b/shared/tinyusb/mp_usbd.c index 74b3f07492..436314dcde 100644 --- a/shared/tinyusb/mp_usbd.c +++ b/shared/tinyusb/mp_usbd.c @@ -24,42 +24,42 @@ * THE SOFTWARE. */ -#include - #include "py/mpconfig.h" -#include "py/runtime.h" #if MICROPY_HW_ENABLE_USBDEV +#include "mp_usbd.h" + #ifndef NO_QSTR -#include "tusb.h" // TinyUSB is not available when running the string preprocessor #include "device/dcd.h" -#include "device/usbd.h" -#include "device/usbd_pvt.h" #endif -// TinyUSB task function wrapper, as scheduled from the USB IRQ -static void mp_usbd_task_callback(mp_sched_node_t *node); - -extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr); +#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE void mp_usbd_task(void) { tud_task_ext(0, false); } +void mp_usbd_task_callback(mp_sched_node_t *node) { + (void)node; + mp_usbd_task(); +} + +#endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr); + // If -Wl,--wrap=dcd_event_handler is passed to the linker, then this wrapper // will be called and allows MicroPython to schedule the TinyUSB task when // dcd_event_handler() is called from an ISR. TU_ATTR_FAST_FUNC void __wrap_dcd_event_handler(dcd_event_t const *event, bool in_isr) { - static mp_sched_node_t usbd_task_node; - __real_dcd_event_handler(event, in_isr); - mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback); + mp_usbd_schedule_task(); } -static void mp_usbd_task_callback(mp_sched_node_t *node) { - (void)node; - mp_usbd_task(); +TU_ATTR_FAST_FUNC void mp_usbd_schedule_task(void) { + static mp_sched_node_t usbd_task_node; + mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback); } void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) { @@ -72,4 +72,4 @@ void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) { out_str[hex_len] = 0; } -#endif +#endif // MICROPY_HW_ENABLE_USBDEV diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 89f8bf0ee9..ef32348451 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -27,25 +27,110 @@ #ifndef MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H #define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H +#include "py/mpconfig.h" + +#if MICROPY_HW_ENABLE_USBDEV + #include "py/obj.h" +#include "py/objarray.h" +#include "py/runtime.h" + +#ifndef NO_QSTR #include "tusb.h" +#include "device/dcd.h" +#endif -static inline void mp_usbd_init(void) { - // Currently this is a thin wrapper around tusb_init(), however - // runtime USB support will require this to be extended. - tusb_init(); -} - -// Call this to explicitly run the TinyUSB device task. +// Run the TinyUSB device task void mp_usbd_task(void); +// Schedule a call to mp_usbd_task(), even if no USB interrupt has occurred +void mp_usbd_schedule_task(void); + // Function to be implemented in port code. // Can write a string up to MICROPY_HW_USB_DESC_STR_MAX characters long, plus terminating byte. extern void mp_usbd_port_get_serial_number(char *buf); -// Most ports need to write a hexadecimal serial number from a byte array, this +// Most ports need to write a hexadecimal serial number from a byte array. This // is a helper function for this. out_str must be long enough to hold a string of total // length (2 * bytes_len + 1) (including NUL terminator). void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len); +// Length of built-in configuration descriptor +#define MP_USBD_BUILTIN_DESC_CFG_LEN (TUD_CONFIG_DESC_LEN + \ + (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) + \ + (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0) \ + ) + +// Built-in USB device and configuration descriptor values +extern const tusb_desc_device_t mp_usbd_builtin_desc_dev; +extern const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN]; + +void mp_usbd_task_callback(mp_sched_node_t *node); + +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +void mp_usbd_deinit(void); +void mp_usbd_init(void); + +const char *mp_usbd_runtime_string_cb(uint8_t index); + +// Maximum number of pending exceptions per single TinyUSB task execution +#define MP_USBD_MAX_PEND_EXCS 2 + +typedef struct { + mp_obj_base_t base; + + mp_obj_t desc_dev; // Device descriptor bytes + mp_obj_t desc_cfg; // Configuration descriptor bytes + mp_obj_t desc_strs; // List/dict/similar to look up string descriptors by index + + // Runtime device driver callback functions + mp_obj_t open_itf_cb; + mp_obj_t reset_cb; + mp_obj_t control_xfer_cb; + mp_obj_t xfer_cb; + + mp_obj_t builtin_driver; // Points to one of mp_type_usb_device_builtin_nnn + + bool active; // Has the user set the USB device active? + bool trigger; // Has the user requested the active state change (or re-activate)? + + // Temporary pointers for xfer data in progress on each endpoint + // Ensuring they aren't garbage collected until the xfer completes + mp_obj_t xfer_data[CFG_TUD_ENDPPOINT_MAX][2]; + + // Pointer to a memoryview that is reused to refer to various pieces of + // control transfer data that are pushed to USB control transfer + // callbacks. Python code can't rely on the memoryview contents + // to remain valid after the callback returns! + mp_obj_array_t *control_data; + + // Pointers to exceptions thrown inside Python callbacks. See + // usbd_callback_function_n(). + mp_uint_t num_pend_excs; + mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS]; +} mp_obj_usb_device_t; + +// Built-in constant objects, possible values of builtin_driver +// +// (Currently not possible to change built-in drivers at runtime, just enable/disable.) +extern const mp_obj_type_t mp_type_usb_device_builtin_default; +extern const mp_obj_type_t mp_type_usb_device_builtin_none; + +// Return true if any built-in driver is enabled +inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd) { + return usbd->builtin_driver != MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none); +} + +#else // Static USBD drivers only + +static inline void mp_usbd_init(void) { + // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() + extern bool tusb_init(void); + tusb_init(); +} + +#endif + +#endif // MICROPY_HW_ENABLE_USBDEV + #endif // MICROPY_INCLUDED_SHARED_TINYUSB_USBD_H diff --git a/shared/tinyusb/mp_usbd_descriptor.c b/shared/tinyusb/mp_usbd_descriptor.c index 72a2652179..be3473b6b9 100644 --- a/shared/tinyusb/mp_usbd_descriptor.c +++ b/shared/tinyusb/mp_usbd_descriptor.c @@ -31,12 +31,11 @@ #include "tusb.h" #include "mp_usbd.h" -#include "mp_usbd_internal.h" #define USBD_CDC_CMD_MAX_SIZE (8) #define USBD_CDC_IN_OUT_MAX_SIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 64) -const tusb_desc_device_t mp_usbd_desc_device_static = { +const tusb_desc_device_t mp_usbd_builtin_desc_dev = { .bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE, .bcdUSB = 0x0200, @@ -53,8 +52,8 @@ const tusb_desc_device_t mp_usbd_desc_device_static = { .bNumConfigurations = 1, }; -const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = { - TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_STATIC_MAX, USBD_STR_0, USBD_STATIC_DESC_LEN, +const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN] = { + TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_BUILTIN_MAX, USBD_STR_0, MP_USBD_BUILTIN_DESC_CFG_LEN, 0, USBD_MAX_POWER_MA), #if CFG_TUD_CDC @@ -69,51 +68,68 @@ const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = { const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { char serial_buf[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes terminating NUL byte static uint16_t desc_wstr[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes prefix uint16_t - const char *desc_str; + const char *desc_str = NULL; uint16_t desc_len; - switch (index) { - case 0: - desc_wstr[1] = 0x0409; // supported language is English - desc_len = 4; - break; - case USBD_STR_SERIAL: - // TODO: make a port-specific serial number callback - mp_usbd_port_get_serial_number(serial_buf); - desc_str = serial_buf; - break; - case USBD_STR_MANUF: - desc_str = MICROPY_HW_USB_MANUFACTURER_STRING; - break; - case USBD_STR_PRODUCT: - desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING; - break; - #if CFG_TUD_CDC - case USBD_STR_CDC: - desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING; - break; - #endif - #if CFG_TUD_MSC - case USBD_STR_MSC: - desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING; - break; - #endif - default: - desc_str = NULL; - } + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + desc_str = mp_usbd_runtime_string_cb(index); + #endif - if (index != 0) { + if (index == 0) { + // String descriptor 0 is special, see USB 2.0 section 9.6.7 String + // + // Expect any runtime value in desc_str to be a fully formed descriptor if (desc_str == NULL) { - return NULL; // Will STALL the endpoint + desc_str = "\x04\x03\x09\x04"; // Descriptor for "English" } + if (desc_str[0] < sizeof(desc_wstr)) { + memcpy(desc_wstr, desc_str, desc_str[0]); + return desc_wstr; + } + return NULL; // Descriptor length too long (or malformed), stall endpoint + } - // Convert from narrow string to wide string - desc_len = 2; - for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) { - desc_wstr[1 + i] = desc_str[i]; - desc_len += 2; + // Otherwise, generate a "UNICODE" string descriptor from the C string + + if (desc_str == NULL) { + // Fall back to the "static" string + switch (index) { + case USBD_STR_SERIAL: + mp_usbd_port_get_serial_number(serial_buf); + desc_str = serial_buf; + break; + case USBD_STR_MANUF: + desc_str = MICROPY_HW_USB_MANUFACTURER_STRING; + break; + case USBD_STR_PRODUCT: + desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING; + break; + #if CFG_TUD_CDC + case USBD_STR_CDC: + desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING; + break; + #endif + #if CFG_TUD_MSC + case USBD_STR_MSC: + desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING; + break; + #endif + default: + break; } } + + if (desc_str == NULL) { + return NULL; // No string, STALL the endpoint + } + + // Convert from narrow string to wide string + desc_len = 2; + for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) { + desc_wstr[1 + i] = desc_str[i]; + desc_len += 2; + } + // first byte is length (including header), second byte is string type desc_wstr[0] = (TUSB_DESC_STRING << 8) | desc_len; @@ -121,13 +137,21 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { } +#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + const uint8_t *tud_descriptor_device_cb(void) { - return (const void *)&mp_usbd_desc_device_static; + return (const void *)&mp_usbd_builtin_desc_dev; } const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { (void)index; - return mp_usbd_desc_cfg_static; + return mp_usbd_builtin_desc_cfg; } -#endif +#else + +// If runtime device support is enabled, descriptor callbacks are implemented in usbd.c + +#endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +#endif // MICROPY_HW_ENABLE_USBDEV diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c new file mode 100644 index 0000000000..a46c0f85e6 --- /dev/null +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -0,0 +1,554 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Blake W. Felt + * Copyright (c) 2022-2023 Angus Gratton + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include "mp_usbd.h" +#include "py/mpconfig.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/obj.h" +#include "py/objarray.h" +#include "py/objstr.h" +#include "py/runtime.h" + +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +#ifndef NO_QSTR +#include "tusb.h" // TinyUSB is not available when running the string preprocessor +#include "device/dcd.h" +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#endif + +static bool in_usbd_task; // Flags if mp_usbd_task() is processing already + +// Some top-level functions that manage global TinyUSB USBD state, not the +// singleton object visible to Python +static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd); +static void mp_usbd_task_inner(void); + +// Pend an exception raise in a USBD callback to print when safe. +// +// We can't raise any exceptions out of the TinyUSB task, as it may still need +// to do some state cleanup. +// +// The requirement for this becomes very similar to +// mp_call_function_x_protected() for interrupts, but it's more restrictive: if +// the C-based USB-CDC serial port is in use, we can't print from inside a +// TinyUSB callback as it might try to recursively call into TinyUSB to flush +// the CDC port and make room. Therefore, we have to store the exception and +// print it as we exit the TinyUSB task. +// +// (Worse, a single TinyUSB task can process multiple callbacks and therefore generate +// multiple exceptions...) +static void usbd_pend_exception(mp_obj_t exception) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + assert(usbd != NULL); + if (usbd->num_pend_excs < MP_USBD_MAX_PEND_EXCS) { + usbd->pend_excs[usbd->num_pend_excs] = exception; + } + usbd->num_pend_excs++; +} + +// Call a Python function from inside a TinyUSB callback. +// +// Handles any exception using usbd_pend_exception() +static mp_obj_t usbd_callback_function_n(mp_obj_t fun, size_t n_args, const mp_obj_t *args) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t ret = mp_call_function_n_kw(fun, n_args, 0, args); + nlr_pop(); + return ret; + } else { + usbd_pend_exception(MP_OBJ_FROM_PTR(nlr.ret_val)); + return MP_OBJ_NULL; + } +} + +// Return a pointer to the data inside a Python buffer provided in a callback +static void *usbd_get_buffer_in_cb(mp_obj_t obj, mp_uint_t flags) { + mp_buffer_info_t buf_info; + if (obj == mp_const_none) { + // This is only if the user somehow + return NULL; + } else if (mp_get_buffer(obj, &buf_info, flags)) { + return buf_info.buf; + } else { + mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_TypeError, + MP_ERROR_TEXT("object with buffer protocol required")); + usbd_pend_exception(exc); + return NULL; + } +} + +const uint8_t *tud_descriptor_device_cb(void) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + const void *result = NULL; + if (usbd) { + result = usbd_get_buffer_in_cb(usbd->desc_dev, MP_BUFFER_READ); + } + return result ? result : &mp_usbd_builtin_desc_dev; +} + +const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { + (void)index; + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + const void *result = NULL; + if (usbd) { + result = usbd_get_buffer_in_cb(usbd->desc_cfg, MP_BUFFER_READ); + } + return result ? result : &mp_usbd_builtin_desc_cfg; +} + +const char *mp_usbd_runtime_string_cb(uint8_t index) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + nlr_buf_t nlr; + + if (usbd == NULL || usbd->desc_strs == mp_const_none) { + return NULL; + } + + if (nlr_push(&nlr) == 0) { + mp_obj_t res = mp_obj_subscr(usbd->desc_strs, mp_obj_new_int(index), MP_OBJ_SENTINEL); + nlr_pop(); + if (res != mp_const_none) { + return usbd_get_buffer_in_cb(res, MP_BUFFER_READ); + } + } else { + mp_obj_t exception = MP_OBJ_FROM_PTR(nlr.ret_val); + if (!(mp_obj_is_type(exception, &mp_type_KeyError) || mp_obj_is_type(exception, &mp_type_IndexError))) { + // Don't print KeyError or IndexError, allowing dicts or lists to have missing entries. + // but log any more exotic errors that pop up + usbd_pend_exception(exception); + } + } + + return NULL; +} + +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + return false; // Currently no support for Vendor control transfers on the Python side +} + +// Generic "runtime device" TinyUSB class driver, delegates everything to Python callbacks + +static void runtime_dev_init(void) { +} + +static void runtime_dev_reset(uint8_t rhport) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + if (!usbd) { + return; + } + + for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) { + for (int dir = 0; dir < 2; dir++) { + usbd->xfer_data[epnum][dir] = mp_const_none; + } + } + + if (mp_obj_is_callable(usbd->reset_cb)) { + usbd_callback_function_n(usbd->reset_cb, 0, NULL); + } +} + +// Calculate how many interfaces TinyUSB expects us to claim from +// driver open(). +// +// Annoyingly, the calling function (process_set_config() in TinyUSB) knows +// this but doesn't pass the information to us. +// +// The answer is: +// - If an Interface Association Descriptor (IAD) is immediately before itf_desc +// in the configuration descriptor, then claim all of the associated interfaces. +// - Otherwise, claim exactly one interface +// +// Relying on the implementation detail that itf_desc is a pointer inside the +// tud_descriptor_configuration_cb() result. Therefore, we can iterate through +// from the beginning to check for an IAD immediately preceding it. +// +// Returns the number of associated interfaces to claim. +static uint8_t _runtime_dev_count_itfs(tusb_desc_interface_t const *itf_desc) { + const tusb_desc_configuration_t *cfg_desc = (const void *)tud_descriptor_configuration_cb(0); + const uint8_t *p_desc = (const void *)cfg_desc; + const uint8_t *p_end = p_desc + cfg_desc->wTotalLength; + assert(p_desc <= itf_desc && itf_desc < p_end); + while (p_desc != (const void *)itf_desc && p_desc < p_end) { + const uint8_t *next = tu_desc_next(p_desc); + + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION + && next == (const void *)itf_desc) { + const tusb_desc_interface_assoc_t *desc_iad = (const void *)p_desc; + return desc_iad->bInterfaceCount; + } + p_desc = next; + } + return 1; // No IAD found +} + +// Scan the other descriptors after these interface(s) to find the total associated length to claim +// from driver open(). +// +// Total number of interfaces to scan for is assoc_itf_count. +// +// Opens any associated endpoints so they can have transfers submitted against them. +// +// Returns the total number of descriptor bytes to claim. +static uint16_t _runtime_dev_claim_itfs(tusb_desc_interface_t const *itf_desc, uint8_t assoc_itf_count, uint16_t max_len) { + const uint8_t *p_desc = (const void *)itf_desc; + const uint8_t *p_end = p_desc + max_len; + while (p_desc < p_end) { + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) { + if (assoc_itf_count > 0) { + // Claim this interface descriptor + assoc_itf_count--; + } else { + // This is the end of the previous interface's associated descriptors + break; + } + } else if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + // Open any endpoints that we come across + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + bool r = usbd_edpt_open(USBD_RHPORT, (const void *)p_desc); + if (!r) { + mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(MP_ENODEV)); + usbd_pend_exception(exc); + break; + } + } + } + p_desc = tu_desc_next(p_desc); + } + return p_desc - (const uint8_t *)itf_desc; +} + +// TinyUSB "Application driver" open callback. Called when the USB host sets +// configuration. Returns number of bytes to claim from descriptors pointed to +// by itf_desc. +// +// This is a little fiddly as it's called before any compiled-in "static" +// TinyUSB drivers, but we don't want to override those. +// +// Also, TinyUSB expects us to know how many interfaces to claim for each time +// this function is called, and will behave unexpectedly if we claim the wrong +// number of interfaces. However, unlike a "normal" USB driver we don't know at +// compile time how many interfaces we've implemented. Instead, we have to look +// back through the configuration descriptor to figure this out. +static uint16_t runtime_dev_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + + // Runtime USB isn't initialised + if (!usbd) { + return 0; + } + + // If TinyUSB built-in drivers are enabled, don't claim any interface in the static range + if (mp_usb_device_builtin_enabled(usbd) && itf_desc->bInterfaceNumber < USBD_ITF_BUILTIN_MAX) { + return 0; + } + + // Determine the total descriptor length of the interface(s) we are going to claim + uint8_t assoc_itf_count = _runtime_dev_count_itfs(itf_desc); + uint16_t claim_len = _runtime_dev_claim_itfs(itf_desc, assoc_itf_count, max_len); + + // Call the Python callback to allow the driver to start working with these interface(s) + + if (mp_obj_is_callable(usbd->open_itf_cb)) { + // Repurpose the control_data memoryview to point into itf_desc for this one call + usbd->control_data->items = (void *)itf_desc; + usbd->control_data->len = claim_len; + mp_obj_t args[] = { MP_OBJ_FROM_PTR(usbd->control_data) }; + usbd_callback_function_n(usbd->open_itf_cb, 1, args); + usbd->control_data->len = 0; + usbd->control_data->items = NULL; + } + + return claim_len; +} + +static bool runtime_dev_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + mp_obj_t cb_res = mp_const_false; + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + tusb_dir_t dir = request->bmRequestType_bit.direction; + mp_buffer_info_t buf_info; + + if (!usbd) { + return false; + } + + if (mp_obj_is_callable(usbd->control_xfer_cb)) { + usbd->control_data->items = (void *)request; + usbd->control_data->len = sizeof(tusb_control_request_t); + mp_obj_t args[] = { + mp_obj_new_int(stage), + MP_OBJ_FROM_PTR(usbd->control_data), + }; + cb_res = usbd_callback_function_n(usbd->control_xfer_cb, MP_ARRAY_SIZE(args), args); + usbd->control_data->items = NULL; + usbd->control_data->len = 0; + + if (cb_res == MP_OBJ_NULL) { + // Exception occurred in the callback handler, stall this transfer + cb_res = mp_const_false; + } + } + + // Check if callback returned any data to submit + if (mp_get_buffer(cb_res, &buf_info, dir == TUSB_DIR_IN ? MP_BUFFER_READ : MP_BUFFER_RW)) { + bool result = tud_control_xfer(USBD_RHPORT, + request, + buf_info.buf, + buf_info.len); + + if (result) { + // Keep buffer object alive until the transfer completes + usbd->xfer_data[0][dir] = cb_res; + } + + return result; + } else { + // Expect True or False to stall or continue + + if (stage == CONTROL_STAGE_ACK) { + // Allow data to be GCed once it's no longer in use + usbd->xfer_data[0][dir] = mp_const_none; + } + return mp_obj_is_true(cb_res); + } +} + +static bool runtime_dev_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + mp_obj_t ep = mp_obj_new_int(ep_addr); + mp_obj_t cb_res = mp_const_false; + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + if (!usbd) { + return false; + } + + if (mp_obj_is_callable(usbd->xfer_cb)) { + mp_obj_t args[] = { + ep, + MP_OBJ_NEW_SMALL_INT(result), + MP_OBJ_NEW_SMALL_INT(xferred_bytes), + }; + cb_res = usbd_callback_function_n(usbd->xfer_cb, MP_ARRAY_SIZE(args), args); + } + + // Clear any xfer_data for this endpoint + usbd->xfer_data[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = mp_const_none; + + return cb_res != MP_OBJ_NULL && mp_obj_is_true(cb_res); +} + +static usbd_class_driver_t const _runtime_dev_driver = +{ + #if CFG_TUSB_DEBUG >= 2 + .name = "runtime_dev", + #endif + .init = runtime_dev_init, + .reset = runtime_dev_reset, + .open = runtime_dev_open, + .control_xfer_cb = runtime_dev_control_xfer_cb, + .xfer_cb = runtime_dev_xfer_cb, + .sof = NULL +}; + +usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) { + *driver_count = 1; + return &_runtime_dev_driver; +} + + +// Functions below here (named mp_usbd_xyz) apply to the whole TinyUSB C-based subsystem +// and not necessarily the USBD singleton object (named usbd_xyz). + +// To support soft reset clearing USB runtime state, we manage three TinyUSB states: +// +// - "Not initialised" - tusb_inited() returns false, no USB at all). Only way +// back to this state is hard reset. +// +// - "Activated" - tusb_inited() returns true, USB device "connected" at device +// end and available to host. +// +// - "Deactivated" - tusb_inited() returns true, but USB device "disconnected" +// at device end and host can't see it. + + +// Top-level USB device subsystem init. +// +// Makes an on-demand call to mp_usbd_activate(), if USB is needed. +// +// This is called on any soft reset after boot.py runs, or on demand if the +// user activates USB and it hasn't activated yet. +void mp_usbd_init(void) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + bool need_usb; + + if (usbd == NULL) { + // No runtime USB device + #if CFG_TUD_CDC || CFG_TUD_MSC + // Builtin drivers are available, so initialise as defaults + need_usb = true; + #else + // No static drivers, nothing to initialise + need_usb = false; + #endif + } else { + // Otherwise, initialise based on whether runtime USB is active + need_usb = usbd->active; + } + + if (need_usb) { + tusb_init(); // Safe to call redundantly + tud_connect(); // Reconnect if mp_usbd_deinit() has disconnected + } +} + +// Top-level USB device deinit. +// +// This variant is called from soft reset, NULLs out the USB device +// singleton instance from MP_STATE_VM, and disconnects the port. +void mp_usbd_deinit(void) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + MP_STATE_VM(usbd) = MP_OBJ_NULL; + if (usbd) { + // Disconnect if a runtime USB device was active + mp_usbd_disconnect(usbd); + } +} + +// Thin wrapper around tud_disconnect() that tells TinyUSB all endpoints +// have stalled, to prevent it getting confused if a transfer is in progress. +static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd) { + if (!tusb_inited()) { + return; // TinyUSB hasn't initialised + } + + if (usbd) { + // There might be USB transfers in progress right now, so need to stall any live + // endpoints + // + // TODO: figure out if we really need this + for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) { + for (int dir = 0; dir < 2; dir++) { + if (usbd->xfer_data[epnum][dir] != mp_const_none) { + usbd_edpt_stall(USBD_RHPORT, tu_edpt_addr(epnum, dir)); + usbd->xfer_data[epnum][dir] = mp_const_none; + } + } + } + } + + #if MICROPY_HW_USB_CDC + // Ensure no pending static CDC writes, as these can cause TinyUSB to crash + tud_cdc_write_clear(); + #endif + + bool was_connected = tud_connected(); + tud_disconnect(); + if (was_connected) { + mp_hal_delay_ms(50); // TODO: Always??? + } +} + +// Thjs callback is queued by mp_usbd_schedule_task() to process USB later. +void mp_usbd_task_callback(mp_sched_node_t *node) { + if (tud_inited() && !in_usbd_task) { + mp_usbd_task_inner(); + } + // If in_usbd_task is set, it means something else has already manually called + // mp_usbd_task() (most likely: C-based USB-CDC serial port). Now the MP + // scheduler is running inside there and triggering this callback. It's OK + // to skip, the already-running outer TinyUSB task will process all pending + // events before it returns. +} + +// Task function can be called manually to force processing of USB events +// (mostly from USB-CDC serial port when blocking.) +void mp_usbd_task(void) { + if (in_usbd_task) { + // If this exception triggers, it means a USB callback tried to do + // something that itself became blocked on TinyUSB (most likely: read or + // write from a C-based USB-CDC serial port.) + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TinyUSB callback can't recurse")); + } + + mp_usbd_task_inner(); +} + +static void mp_usbd_task_inner(void) { + in_usbd_task = true; + + tud_task_ext(0, false); + + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + + // Check for a triggered change to/from active state + if (usbd && usbd->trigger) { + if (usbd->active) { + if (tud_connected()) { + // If a SETUP packet has been received, first disconnect + // and wait for the host to recognise this and trigger a bus reset. + // + // Effectively this forces it to re-enumerate the device. + mp_usbd_disconnect(usbd); + } + tud_connect(); + } else { + mp_usbd_disconnect(usbd); + } + usbd->trigger = false; + } + + in_usbd_task = false; + + if (usbd) { + // Print any exceptions that were raised by Python callbacks + // inside tud_task_ext(). See usbd_callback_function_n. + + // As printing exceptions to USB-CDC may recursively call mp_usbd_task(), + // first copy out the pending data to the local stack + mp_uint_t num_pend_excs = usbd->num_pend_excs; + mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS]; + for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) { + pend_excs[i] = usbd->pend_excs[i]; + usbd->pend_excs[i] = mp_const_none; + } + usbd->num_pend_excs = 0; + + // Now print the exceptions stored from this mp_usbd_task() call + for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) { + mp_obj_print_exception(&mp_plat_print, pend_excs[i]); + } + if (num_pend_excs > MP_USBD_MAX_PEND_EXCS) { + mp_printf(&mp_plat_print, "%u additional exceptions in USB callbacks\n", + num_pend_excs - MP_USBD_MAX_PEND_EXCS); + } + } +} + +#endif // MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index 266cb88cc2..c5644e3d58 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -31,6 +31,10 @@ #if MICROPY_HW_ENABLE_USBDEV +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE 0 +#endif + #ifndef MICROPY_HW_USB_MANUFACTURER_STRING #define MICROPY_HW_USB_MANUFACTURER_STRING "MicroPython" #endif @@ -86,12 +90,9 @@ #define CFG_TUD_MSC_BUFSIZE (MICROPY_FATFS_MAX_SS) #endif -// Define static descriptor size and interface count based on the above config +#define USBD_RHPORT (0) // Currently only one port is supported -#define USBD_STATIC_DESC_LEN (TUD_CONFIG_DESC_LEN + \ - (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) + \ - (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0) \ - ) +// Define built-in interface, string and endpoint numbering based on the above config #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -126,19 +127,19 @@ #endif // CFG_TUD_CDC #endif // CFG_TUD_MSC -/* Limits of statically defined USB interfaces, endpoints, strings */ +/* Limits of builtin USB interfaces, endpoints, strings */ #if CFG_TUD_MSC -#define USBD_ITF_STATIC_MAX (USBD_ITF_MSC + 1) -#define USBD_STR_STATIC_MAX (USBD_STR_MSC + 1) -#define USBD_EP_STATIC_MAX (EPNUM_MSC_OUT + 1) +#define USBD_ITF_BUILTIN_MAX (USBD_ITF_MSC + 1) +#define USBD_STR_BUILTIN_MAX (USBD_STR_MSC + 1) +#define USBD_EP_BUILTIN_MAX (EPNUM_MSC_OUT + 1) #elif CFG_TUD_CDC -#define USBD_ITF_STATIC_MAX (USBD_ITF_CDC + 2) -#define USBD_STR_STATIC_MAX (USBD_STR_CDC + 1) -#define USBD_EP_STATIC_MAX (((EPNUM_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1) +#define USBD_ITF_BUILTIN_MAX (USBD_ITF_CDC + 2) +#define USBD_STR_BUILTIN_MAX (USBD_STR_CDC + 1) +#define USBD_EP_BUILTIN_MAX (((USBD_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1) #else // !CFG_TUD_MSC && !CFG_TUD_CDC -#define USBD_ITF_STATIC_MAX (0) -#define USBD_STR_STATIC_MAX (0) -#define USBD_EP_STATIC_MAX (0) +#define USBD_ITF_BUILTIN_MAX (0) +#define USBD_STR_BUILTIN_MAX (0) +#define USBD_EP_BUILTIN_MAX (0) #endif #endif // MICROPY_HW_ENABLE_USBDEV diff --git a/tests/basics/deque1.py b/tests/basics/deque1.py index 8b7874e2b1..188069bcbf 100644 --- a/tests/basics/deque1.py +++ b/tests/basics/deque1.py @@ -63,3 +63,52 @@ try: ~d except TypeError: print("TypeError") + + +# Same tests, but now with pop() and appendleft() + +d = deque((), 2) +print(len(d)) +print(bool(d)) + +try: + d.popleft() +except IndexError: + print("IndexError") + +print(d.append(1)) +print(len(d)) +print(bool(d)) +print(d.popleft()) +print(len(d)) + +d.append(2) +print(d.popleft()) + +d.append(3) +d.append(4) +print(len(d)) +print(d.popleft(), d.popleft()) +try: + d.popleft() +except IndexError: + print("IndexError") + +d.append(5) +d.append(6) +d.append(7) +print(len(d)) +print(d.popleft(), d.popleft()) +print(len(d)) +try: + d.popleft() +except IndexError: + print("IndexError") + +d = deque((), 2) +d.appendleft(1) +d.appendleft(2) +d.appendleft(3) +d.appendleft(4) +d.appendleft(5) +print(d.pop(), d.pop()) \ No newline at end of file diff --git a/tests/basics/deque2.py b/tests/basics/deque2.py index 80fcd66785..3552d5be37 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 +# iteration over sequence +for x in d: + print(x) + +# Iterables larger than maxlen have the beginnings removed, also tests +# iteration through conversion to list +d = deque([1, 2, 3, 4, 5], 3) +print(list(d)) + +# Empty iterables are also supported +deque([], 10) + +# Extending deques with iterables +d.extend([6, 7]) +print(list(d)) + +# Accessing queue elements via index +d = deque((0, 1, 2, 3), 5) +print(d[0], d[1], d[-1]) + +# Writing queue elements via index +d[3] = 5 +print(d[3]) + +# Accessing indices out of bounds raises IndexError try: - deque([1, 2, 3], 10) -except ValueError: - print("ValueError") - -# Not even empty list, only empty tuple -try: - deque([], 10) -except ValueError: - print("ValueError") - -# Only fixed-size deques are supported, so length arg is mandatory -try: - deque(()) -except TypeError: - print("TypeError") - -d = deque((), 2, True) - -try: - d.popleft() + d[4] except IndexError: print("IndexError") -print(d.append(1)) -print(d.popleft()) - -d.append(2) -print(d.popleft()) - -d.append(3) -d.append(4) -print(d.popleft(), d.popleft()) try: - d.popleft() -except IndexError as e: - print(repr(e)) - -d.append(5) -d.append(6) -print(len(d)) -try: - d.append(7) -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)) + 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 0000000000..5f32bbc496 --- /dev/null +++ b/tests/basics/deque_micropython.py @@ -0,0 +1,75 @@ +# Test MicroPython-specific features of collections.deque. + +try: + from collections import deque +except ImportError: + print("SKIP") + raise SystemExit + + +# Only fixed-size deques are supported, so length arg is mandatory. +try: + deque(()) +except TypeError: + print("TypeError") + +# Test third argument: flags when True means check for under/overflow +d = deque((), 2, True) + +try: + d.popleft() +except IndexError: + print("IndexError") + +try: + d.pop() +except IndexError: + print("IndexError") + +# Removing elements with del is not supported, fallback to +# mp_obj_subscr() error message. +try: + del d[0] +except TypeError: + print("TypeError") + +print(d.append(1)) +print(d.popleft()) + +d.append(2) +print(d.popleft()) + +d.append(3) +d.append(4) +print(d.popleft(), d.popleft()) +try: + d.popleft() +except IndexError as e: + print(repr(e)) + +try: + d.pop() +except IndexError as e: + print(repr(e)) + +d.append(5) +d.append(6) +print(len(d)) +try: + d.append(7) +except IndexError as e: + print(repr(e)) + +try: + d.appendleft(8) +except IndexError as e: + print(repr(e)) + +print(len(d)) + +print(d.popleft(), d.popleft()) +print(len(d)) +try: + d.popleft() +except IndexError as e: + print(repr(e)) diff --git a/tests/basics/deque2.py.exp b/tests/basics/deque_micropython.py.exp similarity index 63% rename from tests/basics/deque2.py.exp rename to tests/basics/deque_micropython.py.exp index 3df8acf405..f1ff7b77ac 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 0000000000..367aeea3a1 --- /dev/null +++ b/tests/basics/deque_slice.py @@ -0,0 +1,29 @@ +try: + from collections import deque +except ImportError: + print("SKIP") + raise SystemExit + +d = deque((), 10) + +d.append(1) +d.append(2) +d.append(3) + +# Index slicing for reads is not supported in CPython +try: + d[0:1] +except TypeError: + print("TypeError") + +# Index slicing for writes is not supported in CPython +try: + d[0:1] = (-1, -2) +except TypeError: + print("TypeError") + +# Index slicing for writes is not supported in CPython +try: + del d[0:1] +except TypeError: + print("TypeError") diff --git a/tests/float/float_struct.py b/tests/float/float_struct.py index 47fe405018..6e282a6afe 100644 --- a/tests/float/float_struct.py +++ b/tests/float/float_struct.py @@ -8,7 +8,7 @@ except ImportError: i = 1.0 + 1 / 2 # TODO: it looks like '=' format modifier is not yet supported # for fmt in ('f', 'd', '>f', '>d', 'f", ">d", "e", ">f", ">d", " int: return []") +# can't do unary op of incompatible type +test("@micropython.viper\ndef f(x:ptr): -x") + # can't do binary op between incompatible types test("@micropython.viper\ndef f(): 1 + []") test("@micropython.viper\ndef f(x:int, y:uint): x < y") @@ -69,9 +72,7 @@ test("@micropython.viper\ndef f(x:ptr32): x[x] = None") test("@micropython.viper\ndef f(): raise 1") # unary ops not implemented -test("@micropython.viper\ndef f(x:int): +x") -test("@micropython.viper\ndef f(x:int): -x") -test("@micropython.viper\ndef f(x:int): ~x") +test("@micropython.viper\ndef f(x:int): not x") # binary op not implemented test("@micropython.viper\ndef f(x:uint, y:uint): res = x // y") diff --git a/tests/micropython/viper_error.py.exp b/tests/micropython/viper_error.py.exp index 31c85b1d87..51cbd6c709 100644 --- a/tests/micropython/viper_error.py.exp +++ b/tests/micropython/viper_error.py.exp @@ -5,6 +5,7 @@ ViperTypeError("local 'x' used before type known",) ViperTypeError("local 'x' has type 'int' but source is 'object'",) ViperTypeError("can't implicitly convert 'ptr' to 'bool'",) ViperTypeError("return expected 'int' but got 'object'",) +ViperTypeError("can't do unary op of 'ptr'",) ViperTypeError("can't do binary op between 'int' and 'object'",) ViperTypeError('comparison of int and uint',) ViperTypeError("can't load from 'int'",) @@ -15,9 +16,7 @@ ViperTypeError("can't store to 'int'",) ViperTypeError("can't store 'None'",) ViperTypeError("can't store 'None'",) ViperTypeError('must raise an object',) -ViperTypeError('unary op __pos__ not implemented',) -ViperTypeError('unary op __neg__ not implemented',) -ViperTypeError('unary op __invert__ not implemented',) +ViperTypeError("'not' not implemented",) ViperTypeError('div/mod not implemented for uint',) ViperTypeError('div/mod not implemented for uint',) ViperTypeError('binary op not implemented',) diff --git a/tests/micropython/viper_unop.py b/tests/micropython/viper_unop.py new file mode 100644 index 0000000000..61cbd5125f --- /dev/null +++ b/tests/micropython/viper_unop.py @@ -0,0 +1,31 @@ +# test unary operators + + +@micropython.viper +def pos(x: int) -> int: + return +x + + +print(pos(0)) +print(pos(1)) +print(pos(-2)) + + +@micropython.viper +def neg(x: int) -> int: + return -x + + +print(neg(0)) +print(neg(1)) +print(neg(-2)) + + +@micropython.viper +def inv(x: int) -> int: + return ~x + + +print(inv(0)) +print(inv(1)) +print(inv(-2)) diff --git a/tests/micropython/viper_unop.py.exp b/tests/micropython/viper_unop.py.exp new file mode 100644 index 0000000000..6d93312caa --- /dev/null +++ b/tests/micropython/viper_unop.py.exp @@ -0,0 +1,9 @@ +0 +1 +-2 +0 +-1 +2 +-1 +-2 +1 diff --git a/tools/ci.sh b/tools/ci.sh index 3cbc51cfad..f94f23893b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -366,6 +366,12 @@ function ci_stm32_nucleo_build { diff $BUILD_WB55/firmware.unpack.dfu $BUILD_WB55/firmware.unpack_no_sk.dfu } +function ci_stm32_misc_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA submodules + make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA +} + ######################################################################################## # ports/unix diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 35c06a3028..06ccd6a1da 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -1730,10 +1730,13 @@ def merge_mpy(compiled_modules, output_file): bytecode.append(0b00000010) # prelude size (n_info=1, n_cell=0) bytecode.extend(b"\x00") # simple_name: qstr index 0 (will use source filename) for idx in range(len(compiled_modules)): - bytecode.append(0x32) # MP_BC_MAKE_FUNCTION - bytecode.append(idx) # index raw code - bytecode.extend(b"\x34\x00\x59") # MP_BC_CALL_FUNCTION, 0 args, MP_BC_POP_TOP - bytecode.extend(b"\x51\x63") # MP_BC_LOAD_NONE, MP_BC_RETURN_VALUE + bytecode.append(Opcode.MP_BC_MAKE_FUNCTION) + bytecode.extend(mp_encode_uint(idx)) # index of raw code + bytecode.append(Opcode.MP_BC_CALL_FUNCTION) + bytecode.append(0) # 0 arguments + bytecode.append(Opcode.MP_BC_POP_TOP) + bytecode.append(Opcode.MP_BC_LOAD_CONST_NONE) + bytecode.append(Opcode.MP_BC_RETURN_VALUE) merged_mpy.extend(mp_encode_uint(len(bytecode) << 3 | 1 << 2)) # length, has_children merged_mpy.extend(bytecode)