rogue
Loading...
Searching...
No Matches
SrpV3Emulation.cpp
Go to the documentation of this file.
1
17#include "rogue/Directives.h"
18
20
21#include <inttypes.h>
22#include <stdint.h>
23
24#include <cstring>
25#include <memory>
26#include <random>
27#include <utility>
28#include <vector>
29
30#include "rogue/GilRelease.h"
31#include "rogue/Logging.h"
37
38namespace rps = rogue::protocols::srp;
40
41#ifndef NO_PYTHON
42 #include <boost/python.hpp>
43namespace bp = boost::python;
44#endif
45
47rps::SrpV3EmulationPtr rps::SrpV3Emulation::create() {
48 rps::SrpV3EmulationPtr p = std::make_shared<rps::SrpV3Emulation>();
49 return (p);
50}
51
53void rps::SrpV3Emulation::setup_python() {
54#ifndef NO_PYTHON
55 bp::class_<rps::SrpV3Emulation, rps::SrpV3EmulationPtr, bp::bases<ris::Master, ris::Slave>, boost::noncopyable>(
56 "SrpV3Emulation",
57 bp::init<>());
58 bp::implicitly_convertible<rps::SrpV3EmulationPtr, ris::MasterPtr>();
59 bp::implicitly_convertible<rps::SrpV3EmulationPtr, ris::SlavePtr>();
60#endif
61}
62
64rps::SrpV3Emulation::SrpV3Emulation() : ris::Master(), ris::Slave() {
65 log_ = rogue::Logging::create("SrpV3Emulation");
66 totAlloc_ = 0;
67 threadEn_ = true;
68 thread_ = std::thread(&SrpV3Emulation::runThread, this);
69}
70
72rps::SrpV3Emulation::~SrpV3Emulation() {
73 stop();
74
75 MemoryMap::iterator it = memMap_.begin();
76 while (it != memMap_.end()) {
77 free(it->second);
78 ++it;
79 }
80}
81
83void rps::SrpV3Emulation::stop() {
84 {
85 std::lock_guard<std::mutex> lock(queMtx_);
86 threadEn_ = false;
87 }
88 queCond_.notify_all();
89
90 // Release the GIL: worker may be mid-sendFrame to a Python slave.
91 {
93 if (thread_.joinable()) thread_.join();
94 }
95
96 ris::Master::stop();
97}
98
100void rps::SrpV3Emulation::runThread() {
101 ris::FramePtr frame;
102 log_->debug("Worker thread started");
103
104 while (threadEn_) {
105 {
106 std::unique_lock<std::mutex> lock(queMtx_);
107 queCond_.wait(lock, [this] { return !queue_.empty() || !threadEn_; });
108
109 if (!threadEn_) break;
110
111 frame = queue_.front();
112 queue_.pop();
113 }
114
115 processFrame(frame);
116 }
117
118 log_->debug("Worker thread stopped");
119}
120
122uint8_t* rps::SrpV3Emulation::allocatePage(uint64_t addr4k) {
123 uint8_t* page = reinterpret_cast<uint8_t*>(malloc(0x1000));
124 if (page == nullptr) {
125 log_->error("Failed to allocate page at 0x%" PRIx64, addr4k);
126 return nullptr;
127 }
128
129 // Fill with random data to emulate uninitialized hardware memory
130 std::mt19937 gen(std::random_device {}());
131 uint32_t* p32 = reinterpret_cast<uint32_t*>(page);
132 for (size_t i = 0; i < 0x1000 / 4; i++) p32[i] = gen();
133
134 memMap_.insert(std::make_pair(addr4k, page));
135 totAlloc_++;
136 log_->debug("Allocating page at 0x%" PRIx64 ". Total pages %" PRIu32, addr4k, totAlloc_);
137 return page;
138}
139
141void rps::SrpV3Emulation::readMemory(uint64_t address, uint8_t* data, uint32_t size) {
142 uint64_t addr4k;
143 uint64_t off4k;
144 uint64_t size4k;
145
146 while (size > 0) {
147 addr4k = (address / 0x1000) * 0x1000;
148 off4k = address % 0x1000;
149 size4k = (addr4k + 0x1000) - (addr4k + off4k);
150
151 if (size4k > size) size4k = size;
152
153 auto it = memMap_.find(addr4k);
154 if (it == memMap_.end()) {
155 // Allocate page with random data on first read (like uninitialized SRAM)
156 uint8_t* page = allocatePage(addr4k);
157 if (page == nullptr) return;
158 memcpy(data, page + off4k, size4k);
159 } else {
160 memcpy(data, it->second + off4k, size4k);
161 }
162
163 size -= size4k;
164 address += size4k;
165 data += size4k;
166 }
167}
168
170void rps::SrpV3Emulation::writeMemory(uint64_t address, const uint8_t* data, uint32_t size) {
171 uint64_t addr4k;
172 uint64_t off4k;
173 uint64_t size4k;
174
175 while (size > 0) {
176 addr4k = (address / 0x1000) * 0x1000;
177 off4k = address % 0x1000;
178 size4k = (addr4k + 0x1000) - (addr4k + off4k);
179
180 if (size4k > size) size4k = size;
181
182 auto it = memMap_.find(addr4k);
183 if (it == memMap_.end()) {
184 uint8_t* page = allocatePage(addr4k);
185 if (page == nullptr) return;
186 it = memMap_.find(addr4k);
187 }
188
189 memcpy(it->second + off4k, data, size4k);
190
191 size -= size4k;
192 address += size4k;
193 data += size4k;
194 }
195}
196
198void rps::SrpV3Emulation::acceptFrame(ris::FramePtr frame) {
199 {
200 std::lock_guard<std::mutex> lock(queMtx_);
201 queue_.push(frame);
202 }
203 queCond_.notify_one();
204}
205
207void rps::SrpV3Emulation::processFrame(ris::FramePtr frame) {
208 uint32_t header[HeadLen / 4];
209 uint32_t tail[TailLen / 4];
210 uint32_t fSize;
211 uint32_t opCode;
212 uint32_t id;
213 uint64_t address;
214 uint32_t reqSize;
215 bool doWrite;
216 bool isPost;
217
218 rogue::GilRelease noGil;
219 ris::FrameLockPtr frLock = frame->lock();
220
221 if (frame->getError()) {
222 log_->warning("Got errored frame = 0x%" PRIx8, frame->getError());
223 return;
224 }
225
226 // Check minimum frame size
227 fSize = frame->getPayload();
228 if (fSize < HeadLen) {
229 log_->warning("Got undersized frame size = %" PRIu32, fSize);
230 return;
231 }
232
233 // Read header
234 ris::FrameIterator fIter = frame->begin();
235 ris::fromFrame(fIter, HeadLen, header);
236
237 // Verify SRPv3 version
238 if ((header[0] & 0xFF) != 0x03) {
239 log_->warning("Got non-SRPv3 frame, version = 0x%" PRIx32, header[0] & 0xFF);
240 return;
241 }
242
243 // Extract fields
244 opCode = (header[0] >> 8) & 0x3;
245 id = header[1];
246 address = (static_cast<uint64_t>(header[3]) << 32) | header[2];
247 reqSize = header[4] + 1;
248
249 // Validate opCode: 0=read, 1=write, 2=posted write
250 if (opCode == 0x3) {
251 log_->warning("Got invalid SRPv3 opcode 0x3 for id=%" PRIu32, id);
252 return;
253 }
254
255 // Match the public SrpV3 client contract: request size is encoded as N-1
256 // and only valid for aligned sizes in the inclusive range [4, 4096].
257 if (reqSize < 4 || reqSize > 4096 || (reqSize & 0x3) != 0) {
258 log_->warning("Got invalid SRPv3 request size=%" PRIu32 " for id=%" PRIu32, reqSize, id);
259 return;
260 }
261
262 doWrite = (opCode == 0x1); // Write
263 isPost = (opCode == 0x2); // Posted write
264
265 log_->debug("Got request id=%" PRIu32 ", opCode=%" PRIu32 ", addr=0x%" PRIx64 ", size=%" PRIu32,
266 id,
267 opCode,
268 address,
269 reqSize);
270
271 // Release the incoming frame lock before building/sending response
272 frLock.reset();
273
274 {
275 std::lock_guard<std::mutex> lock(memMtx_);
276
277 if (doWrite || isPost) {
278 // Verify frame contains write data
279 if (fSize < HeadLen + reqSize) {
280 log_->warning("Write frame too small: got %" PRIu32 ", need %" PRIu32, fSize, HeadLen + reqSize);
281 return;
282 }
283
284 // Re-lock and read write data from frame
285 frLock = frame->lock();
286 fIter = frame->begin() + HeadLen;
287 std::vector<uint8_t> wrData(reqSize);
288 ris::fromFrame(fIter, reqSize, wrData.data());
289 frLock.reset();
290
291 writeMemory(address, wrData.data(), reqSize);
292
293 log_->debug("Write complete id=%" PRIu32 ", addr=0x%" PRIx64 ", size=%" PRIu32, id, address, reqSize);
294 }
295
296 // Posted writes do not get a response
297 if (isPost) return;
298
299 // Build response frame
300 uint32_t respLen = HeadLen + reqSize + TailLen;
301 ris::FramePtr respFrame = reqFrame(respLen, true);
302 respFrame->setPayload(respLen);
303 ris::FrameIterator rIter = respFrame->begin();
304
305 // Write header (echo back the request header)
306 ris::toFrame(rIter, HeadLen, header);
307
308 // Write data payload (read from internal memory)
309 std::vector<uint8_t> rdData(reqSize);
310 readMemory(address, rdData.data(), reqSize);
311 ris::toFrame(rIter, reqSize, rdData.data());
312
313 // Write tail (status = 0 = success)
314 tail[0] = 0;
315 ris::toFrame(rIter, TailLen, tail);
316
317 log_->debug("Sending response id=%" PRIu32 ", size=%" PRIu32, id, respLen);
318
319 sendFrame(respFrame);
320 }
321}
RAII helper that releases the Python GIL for a scope.
Definition GilRelease.h:36
static std::shared_ptr< rogue::Logging > create(const std::string &name, bool quiet=false)
Creates a logger instance.
Definition Logging.cpp:95
Random-access byte iterator across a Frame payload.
static void fromFrame(rogue::interfaces::stream::FrameIterator &iter, uint32_t size, void *dst)
Copies bytes from a frame iterator to a destination pointer.
std::shared_ptr< rogue::interfaces::stream::Frame > FramePtr
Shared pointer alias for Frame.
Definition Frame.h:549
static void toFrame(rogue::interfaces::stream::FrameIterator &iter, uint32_t size, void *src)
Copies bytes from a source pointer into a frame iterator.
std::shared_ptr< rogue::interfaces::stream::FrameLock > FrameLockPtr
Shared pointer alias for FrameLock.
Definition FrameLock.h:110
std::shared_ptr< rogue::protocols::srp::SrpV3Emulation > SrpV3EmulationPtr