1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "mpmd_devices.hpp"
#include "mpmd_impl.hpp"
#include <uhd/device.hpp>
#include <uhd/utils/static.hpp>
#include <uhdlib/rfnoc/rfnoc_device.hpp>
// Need this import because pybind doesn't have an equivalent to Py_IsInitialized()
#include <Python.h>
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <memory>
using namespace uhd;
using namespace uhd::mpmd;
using namespace std::chrono_literals;
namespace py = pybind11;
constexpr auto SIMULATOR_EXIT_TIMEOUT = 5s;
constexpr auto SIMULATOR_STARTUP_TIMEOUT = 5s;
// There can only be one python interpreter instantiated at a time
// The guard means we only destroy the interpreter if we created it
static std::unique_ptr<py::scoped_interpreter> interpreter_guard;
void ensure_python_interpreter()
{
// This call is needed because the interpreter may already be running
// i.e. UHD is being called from python through pyuhd
if (not Py_IsInitialized()) {
interpreter_guard = std::make_unique<py::scoped_interpreter>();
}
}
py::object get_simulator_module()
{
try {
return py::module::import("usrp_mpm.process_manager");
} catch (const py::error_already_set& ex) {
std::string message("Simulator failed to import: ");
message.append(ex.what());
message.append("\nPYTHONPATH: ");
auto pythonpath =
py::str(py::module::import("sys").attr("path")).cast<std::string>();
message.append(pythonpath);
throw std::runtime_error(message);
}
}
device_addrs_t mpmd_find_with_addr(
const std::string& mgmt_addr, const device_addr_t& hint_);
void shutdown_process(py::object& process_manager)
{
// TODO: Sometimes during a TX, the simulator gets shutdown before all of the packets
// are sent
py::object stop_fn = process_manager.attr("stop");
const double timeout_floating =
std::chrono::duration<double>(SIMULATOR_EXIT_TIMEOUT).count();
const bool result = stop_fn(timeout_floating).cast<bool>();
if (!result) {
UHD_LOG_WARNING("SIM",
"Simulator Subprocess did not exit, manual cleanup of subprocesses may "
"be necessary.")
process_manager.attr("terminate")();
}
}
class sim_impl : public mpmd_impl
{
public:
sim_impl(const uhd::device_addr_t& device_addr, py::object process_manager)
: mpmd_impl(device_addr), _process_manager(std::move(process_manager))
{
}
~sim_impl() override
{
// Destroys the mb_ifaces, causing mpm to be unclaimed before shutting down the
// simulator
_deinit();
shutdown_process(_process_manager);
}
private:
// This is an object of type ProcessManager
// See mpm/python/usrp_mpm/process_manager.py
py::object _process_manager;
};
device_addrs_t sim_find(const device_addr_t& hint_)
{
device_addrs_t simulators;
if (hint_.has_key("type") && hint_["type"] == "sim") {
simulators.push_back(hint_);
// Set addr to localhost
simulators.back()["addr"] = "127.0.0.1";
simulators.back()["mgmt_addr"] = "127.0.0.1";
// So discovery doesn't complain about hint mismatch
simulators.back()["type"] = MPM_CATCHALL_DEVICE_TYPE;
}
return simulators;
}
/*! Ensure that the simulator is loaded by pinging the discovery port until it responds or
* the function times out
*/
bool check_simulator_status(
const device_addr_t& device_addr, std::chrono::milliseconds timeout)
{
const auto timeout_time = std::chrono::steady_clock::now() + timeout;
while (std::chrono::steady_clock::now() < timeout_time) {
const auto devices = mpmd_find_with_addr(device_addr["mgmt_addr"], device_addr);
if (!devices.empty()) {
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return false;
}
device::sptr sim_make(const device_addr_t& device_args)
{
// Ensure the interpreter is loaded
ensure_python_interpreter();
py::object manager_module = get_simulator_module();
py::object manager_class = manager_module.attr("ProcessManager");
std::string config_arg("--default-args=config=");
if (not device_args.has_key("config")) {
throw std::runtime_error(
"Please specify a config file using the args key 'config'");
}
config_arg.append(device_args["config"]);
py::list process_args;
process_args.append(py::str(config_arg));
if (device_args.has_key("log_level")) {
std::string level = device_args["log_level"];
if (level == "trace") {
process_args.append(py::str("-vv"));
} else if (level == "debug") {
process_args.append(py::str("-v"));
} else if (level == "info") {
// No-op
} else if (level == "warning") {
process_args.append(py::str("-q"));
} else if (level == "error") {
process_args.append(py::str("-qq"));
}
}
py::object process_manager = manager_class(process_args);
process_manager.attr("start")();
const uint32_t pid = process_manager.attr("pid")().cast<uint32_t>();
UHD_LOG_INFO("SIM", "Starting simulator as pid " << pid);
if (not check_simulator_status(device_args, SIMULATOR_STARTUP_TIMEOUT)) {
shutdown_process(process_manager);
throw std::runtime_error("Simulator Startup timed out!");
}
return static_cast<device::sptr>(
std::make_shared<sim_impl>(device_args, std::move(process_manager)));
}
UHD_STATIC_BLOCK(register_sim_device)
{
device::register_device(&sim_find, &sim_make, device::USRP);
}
|