1.. _module-pw_emu-design: 2 3====== 4Design 5====== 6.. pigweed-module-subpage:: 7 :name: pw_emu 8 9----------- 10Parallelism 11----------- 12 13``pw_emu`` supports running multiple instances simultaneously. This is helpful 14for developers who work on multiple downstream Pigweed projects or who want 15to run multiple tests in parallel on the same machine. 16 17Each instance is identified by a system absolute path that is also used to store 18state about the running instance such as ``pid`` files for running processes, 19current emulator and target, etc. This directory also contains information about 20how to access the emulator channels, e.g. socket ports, PTY paths, etc. 21 22.. mermaid:: 23 24 graph TD; 25 TemporaryEmulator & pw_emu_cli[pw emu cli] <--> Emulator 26 Emulator <--> Launcher & Connector 27 Launcher <--> Handles 28 Connector <--> Handles 29 Launcher <--> Config 30 Handles --Save--> WD --Load--> Handles 31 WD[Working Directory] 32 33----------- 34API surface 35----------- 36 37The implementation uses the following classes: 38 39* :py:class:`pw_emu.frontend.Emulator` - The user-visible API. 40* :py:class:`pw_emu.core.Launcher` - An abstract class that starts an 41 emulator instance for a given configuration and target. 42* :py:class:`pw_emu.core.Connector` - An abstract class that is the 43 interface between a running emulator and the user-visible APIs. 44* :py:class:`pw_emu.core.Handles` - A class that stores specific 45 information about a running emulator instance such as ports to reach emulator 46 channels; it is populated by :py:class:`pw_emu.core.Launcher` and 47 saved in the working directory and used by 48 :py:class:`pw_emu.core.Connector` to access the emulator channels, 49 process PIDs, etc. 50* :py:class:`pw_emu.core.Config` - Loads the ``pw_emu`` configuration and 51 provides helper methods to get and validate configuration options. 52 53------------------- 54Emulator properties 55------------------- 56The implementation exposes the ability to list, read, and write emulator 57properties. The frontend does not abstract properties in a way that is 58emulator-independent or even emulator-target-independent, other than mandating 59that each property is identified by a path. Note that the format of the path is 60also emulator-specific and not standardized. 61 62---- 63QEMU 64---- 65The QEMU frontend is using `QMP <https://wiki.qemu.org/Documentation/QMP>`_ to 66communicate with the running QEMU process and implement emulator-specific 67functionality like reset, list properties, reading properties, etc. 68 69QMP is exposed to the host through two channels: a temporary one to establish 70the initial connection that is used to read the dynamic configuration (e.g. TCP 71ports, PTY paths) and a permanent one that can be used throughout the life of 72the QEMU processes. The frontend is configuring QEMU to expose QMP to a 73``localhost`` TCP port reserved by the frontend and then waiting for QEMU to 74establish the connection on that port. Once the connection is established the 75frontend reads the configuration of the permanent QMP channel (which can be 76either a TCP port or a PTY path) and save it as a channel named ``qmp`` in the 77:py:class:`pw_emu.core.Handles` object. 78 79------ 80Renode 81------ 82The Renode frontend uses `robot port 83<https://renode.readthedocs.io/en/latest/introduction/testing.html>`_ to 84interact with the Renode process. Although the robot interface is designed for 85testing and not as a control interface, it is more robust and better suited to 86be used as a machine interface than the alternative ``monitor`` interface which 87is a user-oriented, ANSI-colored, echoed, log-mixed, telnet interface. 88 89Bugs 90==== 91While Renode allows passing ``0`` for ports to allocate a dynamic port, it does 92not have APIs to retrieve the allocated port. Until support for such a feature 93is added upstream, the implementation is using the following technique to 94allocate a port dynamically: 95 96.. code-block:: py 97 98 sock = socket.socket(socket.SOCK_INET, socket.SOCK_STREAM) 99 sock.bind(('', 0)) 100 _, port = socket.getsockname() 101 sock.close() 102 103There is a race condition that allows another program to fetch the same port, 104but it should work in most light use cases until the issue is properly resolved 105upstream. 106 107---------------- 108More pw_emu docs 109---------------- 110.. include:: docs.rst 111 :start-after: .. pw_emu-nav-start 112 :end-before: .. pw_emu-nav-end 113