29#include <linux/limits.h>
40 : ctxt(ctxt), serviceThread(nullptr) {}
52 }
catch (
const std::exception &e) {
54 std::string(
"failed to request reset: ") + e.what());
70 *
this, engineTypeName, idPath, details, clients);
75 std::unique_ptr<Engine> engine,
78 auto [engineIter, _] =
ownedEngines.emplace(idPath, std::move(engine));
82 Engine *enginePtr = engineIter->second.get();
85 if (prefix.size() > 0)
88 for (
const auto &client : clients) {
89 AppIDPath fullClientPath = prefix + client.relPath;
90 for (
const auto &channel : client.channelAssignments)
91 clientEngines[fullClientPath].setEngine(channel.first, enginePtr);
100 std::unique_ptr<Service> &cacheEntry =
101 serviceCache[make_tuple(std::string(svcType.name()),
id)];
102 if (cacheEntry ==
nullptr) {
109 cacheEntry = std::unique_ptr<Service>(svc);
111 return cacheEntry.get();
117 throw std::runtime_error(
118 "AcceleratorConnection already owns an accelerator");
135 char result[PATH_MAX];
136 ssize_t count = readlink(
"/proc/self/exe", result, PATH_MAX);
138 throw std::runtime_error(
"Could not get executable path");
139 return std::filesystem::path(std::string(result, count));
141 char buffer[MAX_PATH];
142 DWORD length = GetModuleFileNameA(NULL, buffer, MAX_PATH);
144 throw std::runtime_error(
"Could not get executable path");
145 return std::filesystem::path(std::string(buffer, length));
147#eror "Unsupported platform"
156 return std::filesystem::path(std::string(dl_info.dli_fname));
158 HMODULE hModule = NULL;
159 if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
160 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
161 reinterpret_cast<LPCSTR
>(&
getLibPath), &hModule)) {
163 return std::filesystem::path();
166 char buffer[MAX_PATH];
167 DWORD length = GetModuleFileNameA(hModule, buffer, MAX_PATH);
169 throw std::runtime_error(
"Could not get library path");
171 return std::filesystem::path(std::string(buffer, length));
173#eror "Unsupported platform"
179 std::vector<std::filesystem::path> directories;
182 directories.push_back(std::filesystem::current_path());
185 const char *esiBackends = std::getenv(
"ESI_BACKENDS");
189 const char separator =
';';
191 const char separator =
':';
194 std::string pathsStr(esiBackends);
195 std::stringstream ss(pathsStr);
198 while (std::getline(ss, path, separator))
200 directories.emplace_back(path);
204 directories.push_back(
getExePath().parent_path());
206 directories.push_back(
getLibPath().parent_path());
217 backend[0] = toupper(backend[0]);
221 std::string backendFileName =
"lib" + backend +
"Backend.so";
225#if defined(_MSC_VER) && defined(_DEBUG)
226 std::string backendFileName = backend +
"Backend_d.dll";
228 std::string backendFileName = backend +
"Backend.dll";
231#error "Unsupported platform"
235 std::filesystem::path backendPath;
237 std::vector<std::filesystem::path> esiBackendDirs =
240 for (
const auto &dir : esiBackendDirs) {
241 backendPath = dir / backendFileName;
242 logger.
debug(
"CONNECT",
243 "trying to find backend plugin: " + backendPath.string());
244 if (std::filesystem::exists(backendPath)) {
252 backendPath = std::filesystem::absolute(backendPath);
253 logger.
debug(
"CONNECT",
"found backend plugin: " + backendPath.string());
256 backendPath = backendFileName;
257 logger.
debug(
"CONNECT",
258 "trying to find backend plugin: " + backendFileName);
263 void *handle = dlopen(backendPath.string().c_str(), RTLD_NOW | RTLD_GLOBAL);
265 std::string error(dlerror());
266 logger.
error(
"CONNECT",
267 "while attempting to load backend plugin: " + error);
268 throw std::runtime_error(
"While attempting to load backend plugin: " +
275 std::filesystem::path backendPathParent = backendPath.parent_path();
280 if (backendPathParent.empty())
281 backendPathParent = std::filesystem::current_path();
282 logger.
debug(
"CONNECT",
"setting DLL search directory to: " +
283 backendPathParent.string());
284 if (SetDllDirectoryA(backendPathParent.string().c_str()) == 0)
285 throw std::runtime_error(
"While setting DLL directory: " +
286 std::to_string(GetLastError()));
290 HMODULE handle = LoadLibraryA(backendPath.string().c_str());
292 DWORD error = GetLastError();
294 LPSTR messageBuffer =
nullptr;
295 size_t size = FormatMessageA(
296 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
297 FORMAT_MESSAGE_IGNORE_INSERTS,
298 nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
299 (LPSTR)&messageBuffer, 0,
nullptr);
301 std::string errorMessage;
302 if (size > 0 && messageBuffer !=
nullptr) {
303 errorMessage = std::string(messageBuffer, size);
304 LocalFree(messageBuffer);
306 errorMessage =
"Unknown error";
309 std::string fullError =
"While attempting to load backend plugin '" +
310 backendPath.string() +
"': " + errorMessage +
311 " (error code: " + std::to_string(error) +
")";
313 logger.
error(
"CONNECT", fullError);
314 throw std::runtime_error(fullError);
317#eror "Unsupported platform"
319 logger.
info(
"CONNECT",
"loaded backend plugin: " + backendPath.string());
327 static std::map<std::string, BackendCreate> &
get() {
338 if (registry.count(name))
339 throw std::runtime_error(
"Backend already exists in registry");
340 registry[name] = create;
347 std::string connection) {
349 auto f = registry.find(backend);
350 if (f == registry.end()) {
353 f = registry.find(backend);
354 if (f == registry.end()) {
356 details[
"backend"] = backend;
357 std::ostringstream loaded_backends;
359 for (
const auto &b : registry) {
361 loaded_backends <<
", ";
362 loaded_backends << b.first;
365 details[
"loaded_backends"] = loaded_backends.str();
366 getLogger().
error(
"CONNECT",
"backend '" + backend +
"' not found",
368 throw std::runtime_error(
"Backend '" + backend +
"' not found");
371 getLogger().
info(
"CONNECT",
"connecting to backend " + backend +
" via '" +
373 auto conn = f->second(*
this, connection);
374 auto *connPtr = conn.get();
381 void start() {
me = std::thread(&Impl::loop,
this); }
389 addListener(std::initializer_list<ReadChannelPort *> listenPorts,
392 void addTask(std::function<
void(
void)> task) {
393 std::lock_guard<std::mutex> g(
m);
408 std::future<MessageData>>>
415void AcceleratorServiceThread::Impl::loop() {
422 std::vector<std::function<void(
void)>> taskListCopy;
430 std::this_thread::yield();
435 std::lock_guard<std::mutex> g(
m);
436 for (
auto &[channel, cbfPair] :
listeners) {
437 assert(channel &&
"Null channel in listener list");
438 std::future<MessageData> &f = cbfPair.second;
439 if (f.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
440 portUnlockWorkList.emplace_back(channel, cbfPair.first, f.get());
441 f = channel->readAsync();
447 for (
auto [channel, cb, data] : portUnlockWorkList)
448 cb(channel, std::move(data));
451 portUnlockWorkList.clear();
456 std::lock_guard<std::mutex> g(
m);
459 for (
auto &task : taskListCopy)
464void AcceleratorServiceThread::Impl::addListener(
465 std::initializer_list<ReadChannelPort *> listenPorts,
467 std::lock_guard<std::mutex> g(m);
468 for (
auto port : listenPorts) {
469 if (listeners.count(port))
470 throw std::runtime_error(
"Port already has a listener");
471 listeners[port] = std::make_pair(callback, port->readAsync());
492 std::initializer_list<ReadChannelPort *> listenPorts,
495 impl->addListener(listenPorts, callback);
500 impl->addTask([&module]() {
module.poll(); });
514 engine->disconnect();
assert(baseType &&"element must be base type")
Abstract class representing a connection to an accelerator.
virtual Service * createService(Service::Type service, AppIDPath idPath, std::string implName, const ServiceImplDetails &details, const HWClientDetails &clients)=0
Called by getServiceImpl exclusively.
ServiceClass * getService(AppIDPath id={}, std::string implName={}, ServiceImplDetails details={}, HWClientDetails clients={})
Get a typed reference to a particular service type.
virtual bool reset()
Request a reset of the accelerator design.
void clearOwnedObjects()
Drop accelerator-owned objects before a derived backend destroys resources that those objects may ref...
std::map< AppIDPath, BundleEngineMap > clientEngines
Mapping of clients to their servicing engines.
void registerEngine(AppIDPath idPath, std::unique_ptr< Engine > engine, const HWClientDetails &clients)
If createEngine is overridden, this method should be called to register the engine and all of the cha...
std::map< ServiceCacheKey, std::unique_ptr< Service > > serviceCache
std::unique_ptr< AcceleratorServiceThread > serviceThread
std::unique_ptr< Accelerator > ownedAccelerator
Accelerator object owned by this connection.
virtual void disconnect()
Disconnect from the accelerator cleanly.
std::map< AppIDPath, std::unique_ptr< Engine > > ownedEngines
Collection of owned engines.
virtual void createEngine(const std::string &engineTypeName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Create a new engine for channel communication with the accelerator.
virtual ~AcceleratorConnection()
Logger & getLogger() const
AcceleratorServiceThread * getServiceThread()
Return a pointer to the accelerator 'service' thread (or threads).
AcceleratorConnection(Context &ctxt)
Accelerator * takeOwnership(std::unique_ptr< Accelerator > accel)
Assume ownership of an accelerator object.
Background thread which services various requests.
AcceleratorServiceThread()
std::unique_ptr< Impl > impl
void stop()
Instruct the service thread to stop running.
void addPoll(HWModule &module)
Poll this module.
~AcceleratorServiceThread()
void addListener(std::initializer_list< ReadChannelPort * > listenPorts, std::function< void(ReadChannelPort *, MessageData)> callback)
When there's data on any of the listenPorts, call the callback.
Top level accelerator class.
AcceleratorConnections, Accelerators, and Manifests must all share a context.
std::vector< std::unique_ptr< AcceleratorConnection > > connections
AcceleratorConnection * connect(std::string backend, std::string connection)
Connect to an accelerator backend.
Engines implement the actual channel communication between the host and the accelerator.
Represents either the top level or an instance of a hardware module.
virtual void error(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report an error.
virtual void info(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report an informational message.
void debug(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report a debug message.
A concrete flat message backed by a single vector of bytes.
A ChannelPort which reads data from the accelerator.
std::map< std::string, BackendCreate > backendRegistry
static std::map< std::string, BackendCreate > & get()
virtual void write(uint32_t addr, uint64_t data)=0
Write a 64-bit value to the global MMIO space.
static Service * createService(AcceleratorConnection *acc, Service::Type svcType, AppIDPath id, std::string implName, ServiceImplDetails details, HWClientDetails clients)
Create a service instance from the given details.
Parent class of all APIs modeled as 'services'.
const std::type_info & Type
void registerBackend(const std::string &name, BackendCreate create)
std::function< std::unique_ptr< AcceleratorConnection >(Context &, std::string)> BackendCreate
Backends can register themselves to be connected via a connection string.
std::unique_ptr< Engine > createEngine(AcceleratorConnection &conn, const std::string &dmaEngineName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Create an engine by name.
static std::filesystem::path getExePath()
Get the path to the currently running executable.
std::map< std::string, std::any > ServiceImplDetails
static void loadBackend(Context &ctxt, std::string backend)
Load a backend plugin dynamically.
constexpr uint64_t ResetMagicNumber
Magic value which, when written to MMIO offset ResetRequestOffset, requests a design reset.
static std::filesystem::path getLibPath()
Get the path to the currently running shared library.
constexpr uint32_t ResetRequestOffset
Offset into the (global) MMIO space at which to request a design reset.
static std::vector< std::filesystem::path > getESIBackendDirectories()
Get the list of directories to search for backend plugins.
std::vector< HWClientDetail > HWClientDetails
std::map< ReadChannelPort *, std::pair< std::function< void(ReadChannelPort *, MessageData)>, std::future< MessageData > > > listeners
void addTask(std::function< void(void)> task)
void addListener(std::initializer_list< ReadChannelPort * > listenPorts, std::function< void(ReadChannelPort *, MessageData)> callback)
When there's data on any of the listenPorts, call the callback.
std::vector< std::function< void(void)> > taskList
Tasks which should be called on every loop iteration.