CIRCT  19.0.0git
Client.cpp
Go to the documentation of this file.
1 //===- Client.cpp - Cosim RPC client ----------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "CosimDpi.capnp.h"
10 #include "cosim/CapnpThreads.h"
11 #include <capnp/ez-rpc.h>
12 
13 #include <cassert>
14 #include <thread>
15 #ifdef _WIN32
16 #include <io.h>
17 #else
18 #include <unistd.h>
19 #endif
20 
21 using namespace capnp;
22 using namespace esi::cosim;
23 
24 /// Internal implementation to hide all the capnp details.
26 
27  Impl(RpcClient &client, capnp::EzRpcClient &rpcClient)
28  : client(client), waitScope(rpcClient.getWaitScope()), cosim(nullptr),
29  lowLevel(nullptr) {
30  // Get the main interface.
31  cosim = rpcClient.getMain<CosimDpiServer>();
32 
33  // Grab a reference to the low level interface.
34  auto llReq = cosim.openLowLevelRequest();
35  auto llPromise = llReq.send();
36  lowLevel = llPromise.wait(waitScope).getLowLevel();
37 
38  // Get the ESI version and compressed manifest.
39  auto maniResp = cosim.getCompressedManifestRequest().send().wait(waitScope);
40  capnp::Data::Reader data = maniResp.getCompressedManifest();
41  client.esiVersion = maniResp.getVersion();
42  client.compressedManifest = std::vector<uint8_t>(data.begin(), data.end());
43 
44  // Iterate through the endpoints and register them.
45  auto capnpEndpointsResp = cosim.listRequest().send().wait(waitScope);
46  for (const auto &capnpEndpoint : capnpEndpointsResp.getIfaces()) {
47  assert(capnpEndpoint.hasEndpointID() &&
48  "Response did not contain endpoint ID not found!");
49  std::string fromHostType, toHostType;
50  if (capnpEndpoint.hasFromHostType())
51  fromHostType = capnpEndpoint.getFromHostType();
52  if (capnpEndpoint.hasToHostType())
53  toHostType = capnpEndpoint.getToHostType();
54  bool rc = client.endpoints.registerEndpoint(capnpEndpoint.getEndpointID(),
55  fromHostType, toHostType);
56  assert(rc && "Endpoint ID already exists!");
57  Endpoint *ep = client.endpoints[capnpEndpoint.getEndpointID()];
58  // TODO: delay opening until client calls connect().
59  auto openReq = cosim.openRequest();
60  openReq.setIface(capnpEndpoint);
61  EsiDpiEndpoint::Client dpiEp =
62  openReq.send().wait(waitScope).getEndpoint();
63  endpointMap.emplace(ep, dpiEp);
64  }
65  }
66 
68  kj::WaitScope &waitScope;
69  CosimDpiServer::Client cosim;
70  EsiLowLevel::Client lowLevel;
71  std::map<Endpoint *, EsiDpiEndpoint::Client> endpointMap;
72 
73  /// Called from the event loop periodically.
74  // TODO: try to reduce work in here. Ideally, eliminate polling altogether
75  // though I can't figure out how with libkj's event loop.
76  void pollInternal();
77 };
78 
80  // Iterate through the endpoints checking for messages.
81  for (auto &[ep, capnpEp] : endpointMap) {
82  // Process writes to the simulation.
84  if (!ep->getSendTypeId().empty() && ep->getMessageToSim(msg)) {
85  auto req = capnpEp.sendFromHostRequest();
86  req.setMsg(capnp::Data::Reader(msg->getBytes(), msg->getSize()));
87  req.send().detach([](kj::Exception &&e) -> void {
88  throw std::runtime_error("Error sending message to simulation: " +
89  std::string(e.getDescription().cStr()));
90  });
91  }
92 
93  // Process reads from the simulation.
94  // TODO: polling for a response is horribly slow and inefficient. Rework
95  // the capnp protocol to avoid it.
96  if (!ep->getRecvTypeId().empty()) {
97  auto resp = capnpEp.recvToHostRequest().send().wait(waitScope);
98  if (resp.getHasData()) {
99  auto data = resp.getResp();
100  ep->pushMessageToClient(
101  std::make_unique<MessageData>(data.begin(), data.size()));
102  }
103  }
104  }
105 
106  // Process MMIO read requests.
107  if (auto readReq = client.lowLevelBridge.readReqs.pop()) {
108  auto req = lowLevel.readMMIORequest();
109  req.setAddress(*readReq);
110  auto respPromise = req.send();
111  respPromise
112  .then([&](auto resp) -> void {
113  client.lowLevelBridge.readResps.push(
114  std::make_pair(resp.getData(), 0));
115  })
116  .detach([&](kj::Exception &&e) -> void {
117  client.lowLevelBridge.readResps.push(std::make_pair(0, 1));
118  });
119  }
120 
121  // Process MMIO write requests.
122  if (auto writeReq = client.lowLevelBridge.writeReqs.pop()) {
123  auto req = lowLevel.writeMMIORequest();
124  req.setAddress(writeReq->first);
125  req.setData(writeReq->second);
126  req.send()
127  .then([&](auto resp) -> void {
128  client.lowLevelBridge.writeResps.push(0);
129  })
130  .detach([&](kj::Exception &&e) -> void {
131  client.lowLevelBridge.writeResps.push(1);
132  });
133  }
134 }
135 
136 void RpcClient::mainLoop(std::string host, uint16_t port) {
137  capnp::EzRpcClient rpcClient(host, port);
138  kj::WaitScope &waitScope = rpcClient.getWaitScope();
139  Impl impl(*this, rpcClient);
140 
141  // Signal that we're good to go.
142  started.store(true);
143 
144  // Start the event loop. Does not return until stop() is called.
145  loop(waitScope, [&]() { impl.pollInternal(); });
146 }
147 
148 /// Start the client if not already started.
149 void RpcClient::run(std::string host, uint16_t port) {
150  Lock g(m);
151  if (myThread == nullptr) {
152  started.store(false);
153  myThread = new std::thread(&RpcClient::mainLoop, this, host, port);
154  // Spin until the capnp thread is started and ready to go.
155  while (!started.load())
156  std::this_thread::sleep_for(std::chrono::microseconds(10));
157  } else {
158  fprintf(stderr, "Warning: cannot Run() RPC client more than once!");
159  }
160 }
assert(baseType &&"element must be base type")
std::vector< uint8_t > compressedManifest
Definition: CapnpThreads.h:73
EndpointRegistry endpoints
Definition: CapnpThreads.h:65
std::lock_guard< std::mutex > Lock
Definition: CapnpThreads.h:63
bool registerEndpoint(std::string epId, std::string fromHostTypeId, std::string toHostTypeId)
Register an Endpoint.
Definition: Endpoint.cpp:37
Implements a bi-directional, thread-safe bridge between the RPC server and DPI functions.
Definition: Endpoint.h:35
std::unique_ptr< MessageData > MessageDataPtr
Representing messages as shared pointers to vectors may be a performance issue in the future but it i...
Definition: Endpoint.h:40
The Capnp RpcClient.
Definition: CapnpThreads.h:102
Internal implementation to hide all the capnp details.
Definition: Client.cpp:25
kj::WaitScope & waitScope
Definition: Client.cpp:68
CosimDpiServer::Client cosim
Definition: Client.cpp:69
EsiLowLevel::Client lowLevel
Definition: Client.cpp:70
std::map< Endpoint *, EsiDpiEndpoint::Client > endpointMap
Definition: Client.cpp:71
void pollInternal()
Called from the event loop periodically.
Definition: Client.cpp:79
Impl(RpcClient &client, capnp::EzRpcClient &rpcClient)
Definition: Client.cpp:27