xref: /aosp_15_r20/external/pigweed/pw_emu/design.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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