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()
{
// 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);
}
|