Design#

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)

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:

  • pw_emu.frontend.Emulator: the user visible API

  • pw_emu.core.Launcher: an abstract class that starts an emulator instance for a given configuration and target

  • pw_emu.core.Connector: an abstract class that is the interface between a running emulator and the user visible APIs

  • 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 pw_emu.core.Launcher and saved in the working directory and used by pw_emu.core.Connector to access the emulator channels, process pids, etc.

  • 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 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 pw_emu.core.Handles object.

renode#

The renode frontend is using renode’s robot port 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:

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.