aboutsummaryrefslogtreecommitdiff
path: root/pw_emu/design.rst
diff options
context:
space:
mode:
Diffstat (limited to 'pw_emu/design.rst')
-rw-r--r--pw_emu/design.rst103
1 files changed, 103 insertions, 0 deletions
diff --git a/pw_emu/design.rst b/pw_emu/design.rst
new file mode 100644
index 000000000..67e220729
--- /dev/null
+++ b/pw_emu/design.rst
@@ -0,0 +1,103 @@
+.. _module-pw_emu-design:
+
+======
+Design
+======
+.. pigweed-module-subpage::
+ :name: pw_emu
+ :tagline: Emulators frontend
+
+Multiple instances are supported in order to enable developers who work on
+multiple downstream Pigweed projects to work unhindered and also to run multiple
+test instances in parallel on the same machine.
+
+Each instance is identified by a system absolute path that is also used to store
+state about the running instance such as pid files for running processes,
+current emulator and target, etc. This directory also contains information about
+how to access the emulator channels (e.g. socket ports, pty paths)
+
+.. mermaid::
+
+ graph TD;
+ TemporaryEmulator & pw_emu_cli[pw emu cli] <--> Emulator
+ Emulator <--> Launcher & Connector
+ Launcher <--> Handles
+ Connector <--> Handles
+ Launcher <--> Config
+ Handles --Save--> WD --Load--> Handles
+ WD[Working Directory]
+
+
+The implementation uses the following classes:
+
+* :py:class:`pw_emu.frontend.Emulator`: the user visible API
+
+* :py:class:`pw_emu.core.Launcher`: an abstract class that starts an
+ emulator instance for a given configuration and target
+
+* :py:class:`pw_emu.core.Connector`: an abstract class that is the
+ interface between a running emulator and the user visible APIs
+
+* :py:class:`pw_emu.core.Handles`: class that stores specific
+ information about a running emulator instance such as ports to reach emulator
+ channels; it is populated by :py:class:`pw_emu.core.Launcher` and
+ saved in the working directory and used by
+ :py:class:`pw_emu.core.Connector` to access the emulator channels,
+ process pids, etc.
+
+* :py:class:`pw_emu.core.Config`: loads the pw_emu configuration and provides
+ helper methods to get and validate configuration options
+
+-------------------
+Emulator properties
+-------------------
+The implementation exposes the ability to list, read and write emulator
+properties. The frontend does not abstract properties in a way that is emulator
+or even emulator target independent, other than mandating that each property is
+identified by a path. Note that the format of the path is also emulator specific
+and not standardized.
+
+----
+QEMU
+----
+The QEMU frontend is using `QMP <https://wiki.qemu.org/Documentation/QMP>`_ to
+communicate with the running QEMU process and implement emulator specific
+functionality like reset, list or reading properties, etc.
+
+QMP is exposed to the host through two channels: a temporary one to establish
+the initial connection that is used to read the dynamic configuration (e.g. TCP
+ports, pty paths) and a permanent one that can be used thought the life of the
+QEMU processes. The frontend is configuring QEMU to expose QMP to a
+``localhost`` TCP port reserved by the frontend and then waiting for QEMU to
+establish the connection on that port. Once the connection is established the
+frontend will read the configuration of the permanent QMP channel (which can be
+either a TCP port or a PTY path) and save it as a channel named ``qmp`` in the
+:py:class:`pw_emu.core.Handles` object.
+
+------
+renode
+------
+The renode frontend is using `renode's robot port
+<https://renode.readthedocs.io/en/latest/introduction/testing.html>`_ to
+interact with the renode process. Although the robot interface is designed for
+testing and not as a control interface, it is more robust and better suited to
+be used as a machine interface than the alternative ``monitor`` interface which
+is user oriented, ANSI colored, echoed, log mixed, telnet interface.
+
+Bugs
+====
+While renode allows passing 0 for ports to allocate a dynamic port, it does not
+have APIs to retrieve the allocated port. Until support for such a feature is
+added upstream, the implementation is using the following technique to allocate
+a port dynamically:
+
+.. code-block:: python
+
+ sock = socket.socket(socket.SOCK_INET, socket.SOCK_STREAM)
+ sock.bind(('', 0))
+ _, port = socket.getsockname()
+ sock.close()
+
+There is a race condition that allows another program to fetch the same port,
+but it should work in most light use cases until the issue is properly resolved
+upstream.