Skip to content

Commit

Permalink
Adds support to reading and writing numeric variables to the VirtualM…
Browse files Browse the repository at this point in the history
…emorySpace. (#420)
  • Loading branch information
balazsracz authored Sep 1, 2020
1 parent 1e82c83 commit d14cf8b
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 10 deletions.
201 changes: 195 additions & 6 deletions src/openlcb/VirtualMemorySpace.cxxtest
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,28 @@ TEST_F(TestSpaceTest, write_hole)
ASSERT_EQ(0, b->data()->resultCode);
}

CDI_GROUP(NumericGroup);
CDI_GROUP_ENTRY(skipped, EmptyGroup<5>);
CDI_GROUP_ENTRY(first, Uint32ConfigEntry);
CDI_GROUP_ENTRY(second, Int16ConfigEntry);
CDI_GROUP_ENTRY(third, Uint8ConfigEntry);
CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>);
CDI_GROUP_END();

CDI_GROUP(NumericMemorySpace);
CDI_GROUP_ENTRY(skipped, EmptyGroup<5>);
CDI_GROUP_ENTRY(first, StringConfigEntry<13>);
CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>);
CDI_GROUP_ENTRY(second, StringConfigEntry<20>);
CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>);
CDI_GROUP_ENTRY(outer_before, Uint32ConfigEntry);
using RG = RepeatedGroup<NumericGroup, 5>;
CDI_GROUP_ENTRY(grp, RG);
CDI_GROUP_ENTRY(outer_after, Uint32ConfigEntry);
CDI_GROUP_END();

NumericMemorySpace ncfg(44);

class TestSpaceAsync : public VirtualMemorySpace
{
public:
Expand All @@ -258,9 +280,20 @@ public:
arg1.clear();
arg2.clear();
register_string(
cfg.first(), string_reader(&arg1), string_writer(&arg1));
ncfg.first(), string_reader(&arg1), string_writer(&arg1));
register_string(
cfg.second(), string_reader(&arg2), string_writer(&arg2));
ncfg.second(), string_reader(&arg2), string_writer(&arg2));
register_numeric(ncfg.outer_before(), typed_reader(&rnBefore_),
typed_writer(&rnBefore_));
register_numeric(ncfg.outer_after(), typed_reader(&rnAfter_),
typed_writer(&rnAfter_));
register_numeric(ncfg.grp().entry(0).first(), typed_reader(&rnFirst_),
typed_writer(&rnFirst_));
register_numeric(ncfg.grp().entry(0).second(), typed_reader(&rnSecond_),
typed_writer(&rnSecond_));
register_numeric(ncfg.grp().entry(0).third(), typed_reader(&rnThird_),
typed_writer(&rnThird_));
register_repeat(ncfg.grp());
}

/// Creates a ReaderFunction that just returns a string from a given
Expand All @@ -275,6 +308,7 @@ public:
return [this, ptr](
unsigned repeat, string *contents, BarrierNotifiable *done) {
attempt++;
lastRepeat_ = repeat;
if ((attempt & 1) == 0)
{
*contents = *ptr;
Expand All @@ -290,18 +324,45 @@ public:
};
}

/// Creates a TypedReaderFunction that just returns a value from a given
/// variable.
/// @param ptr the variable whose contents to return as read value. Must
/// stay alive as long as the function is in use.
/// @return the TypedReaderFunction.
template <typename T>
typename std::function<T(unsigned repeat, BarrierNotifiable *done)>
typed_reader(T *ptr)
{
return [this, ptr](unsigned repeat, BarrierNotifiable *done) {
attempt++;
lastRepeat_ = repeat;
if ((attempt & 1) == 0)
{
done->notify();
return *ptr;
}
else
{
g_executor.add(
new CallbackExecutable([done]() { done->notify(); }));
return T();
}
};
}

/// Creates a WriterFunction that just stores the data in a given string
/// variable.
/// @param ptr the string whose contents to return as read value. Must stay
/// alive as long as the function is in use.
/// @return the ReaderFunction.
/// @param ptr the variable where to store the contents. Must stay alive as
/// long as the function is in use.
/// @return the WriterFunction.
std::function<void(
unsigned repeat, string contents, BarrierNotifiable *done)>
string_writer(string *ptr)
{
return [this, ptr](
unsigned repeat, string contents, BarrierNotifiable *done) {
attempt++;
lastRepeat_ = repeat;
if ((attempt & 1) == 0)
{
*ptr = std::move(contents);
Expand All @@ -317,6 +378,46 @@ public:
};
}

/// Creates a TypedWriterFunction that just stores the data in a given
/// variable.
/// @param ptr the variable where to store the contents. Must stay alive as
/// long as the function is in use.
/// @return the TypedWriterFunction.
template <typename T>
std::function<void(unsigned repeat, T contents, BarrierNotifiable *done)>
typed_writer(T *ptr)
{
return
[this, ptr](unsigned repeat, T contents, BarrierNotifiable *done) {
attempt++;
lastRepeat_ = repeat;
if ((attempt & 1) == 0)
{
*ptr = std::move(contents);
done->notify();
}
else
{
g_executor.add(
new CallbackExecutable([done]() { done->notify(); }));
}
};
}

/// Stores the last invoked repetition number.
unsigned lastRepeat_ = 0;
/// Shadow for NumericGroup.first.
uint32_t rnFirst_ = 0;
/// Shadow for NumericGroup.second.
int16_t rnSecond_ = 0;
/// Shadow for NumericGroup.third.
uint8_t rnThird_ = 0;

/// Shadow for NUmericMemorySpace.before.
uint32_t rnBefore_ = 0;
/// Shadow for NUmericMemorySpace.after.
uint32_t rnAfter_ = 0;

private:
size_t attempt = 0;
};
Expand All @@ -326,10 +427,11 @@ class TestSpaceAsyncTest : public VirtualMemorySpaceTest
protected:
TestSpaceAsyncTest()
{
space_.reset(new TestSpaceAsync);
space_.reset(tspace_);
memCfg_.registry()->insert(node_, SPACE, space_.get());
}

TestSpaceAsync *tspace_ = new TestSpaceAsync;
/// Memory space number where the test space is registered.
const uint8_t SPACE = 0x52;
};
Expand Down Expand Up @@ -368,6 +470,93 @@ TEST_F(TestSpaceAsyncTest, write_payload_async)
EXPECT_EQ(5u, arg2.size());
}

/// Tests reading and writing numeric variables with endianness.
TEST_F(TestSpaceAsyncTest, rw_numeric_async)
{
string u32payload;
u32payload.push_back(0xAA);
u32payload.push_back(2);
u32payload.push_back(3);
u32payload.push_back(4);

auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE,
NodeHandle(node_->node_id()), SPACE, ncfg.outer_before().offset(),
u32payload);
ASSERT_EQ(0, b->data()->resultCode);
EXPECT_EQ(0xAA020304u, tspace_->rnBefore_);

tspace_->rnBefore_ = 0xbb554433;
b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART,
NodeHandle(node_->node_id()), SPACE, ncfg.outer_before().offset(), 4);
ASSERT_EQ(0, b->data()->resultCode);
EXPECT_EQ((char)0xbb, b->data()->payload[0]);
EXPECT_EQ(0x55, b->data()->payload[1]);
EXPECT_EQ(0x44, b->data()->payload[2]);
EXPECT_EQ(0x33, b->data()->payload[3]);
}

/// Tests variable after repeted group.
TEST_F(TestSpaceAsyncTest, rw_numeric_after_repeat)
{
string u32payload;
u32payload.push_back(0xAA);
u32payload.push_back(2);
u32payload.push_back(3);
u32payload.push_back(4);

auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE,
NodeHandle(node_->node_id()), SPACE, ncfg.outer_after().offset(),
u32payload);
ASSERT_EQ(0, b->data()->resultCode);
EXPECT_EQ(0xAA020304u, tspace_->rnAfter_);

tspace_->rnAfter_ = 0xbb554433;
b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART,
NodeHandle(node_->node_id()), SPACE, ncfg.outer_after().offset(), 4);
ASSERT_EQ(0, b->data()->resultCode);
EXPECT_EQ((char)0xbb, b->data()->payload[0]);
EXPECT_EQ(0x55, b->data()->payload[1]);
EXPECT_EQ(0x44, b->data()->payload[2]);
EXPECT_EQ(0x33, b->data()->payload[3]);
}

/// Tests reading and writing numeric variables with endianness from repetitions.
TEST_F(TestSpaceAsyncTest, rw_numeric_repeat)
{
string u32payload;
u32payload.push_back(0xAA);
u32payload.push_back(2);
u32payload.push_back(3);
u32payload.push_back(4);

auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE,
NodeHandle(node_->node_id()), SPACE,
ncfg.grp().entry(3).first().offset(), u32payload);
ASSERT_EQ(0, b->data()->resultCode);
EXPECT_EQ(3u, tspace_->lastRepeat_);
EXPECT_EQ(0xAA020304u, tspace_->rnFirst_);

tspace_->rnSecond_ = -2;
b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART,
NodeHandle(node_->node_id()), SPACE,
ncfg.grp().entry(4).second().offset(), 2);
ASSERT_EQ(0, b->data()->resultCode);
EXPECT_EQ(4u, tspace_->lastRepeat_);
ASSERT_EQ(2u, b->data()->payload.size());
EXPECT_EQ((char)0xFF, b->data()->payload[0]);
EXPECT_EQ((char)0xFE, b->data()->payload[1]);

tspace_->rnSecond_ = 55;
b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART,
NodeHandle(node_->node_id()), SPACE,
ncfg.grp().entry(0).second().offset(), 2);
ASSERT_EQ(0, b->data()->resultCode);
EXPECT_EQ(0u, tspace_->lastRepeat_);
ASSERT_EQ(2u, b->data()->payload.size());
EXPECT_EQ((char)0, b->data()->payload[0]);
EXPECT_EQ((char)55, b->data()->payload[1]);
}

CDI_GROUP(RepeatMemoryDef);
CDI_GROUP_ENTRY(skipped, EmptyGroup<5>);
CDI_GROUP_ENTRY(before, StringConfigEntry<13>);
Expand Down
46 changes: 42 additions & 4 deletions src/openlcb/VirtualMemorySpace.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,21 @@ protected:
/// @param contents the payload to be returned from this variable shall
/// be written here. Will be zero-padded to size_ bytes if shorter.
/// @param done must be notified when the read values are ready. The
/// call will be re-tried in this case.
/// @return true if the read was successful, false if the read needs to be
/// re-tried later,
using ReadFunction = std::function<bool(
/// call will be re-tried if this does not happen inline.
using ReadFunction = std::function<void(
unsigned repeat, string *contents, BarrierNotifiable *done)>;

/// Typed WriteFunction for primitive types.
template <typename T>
using TypedWriteFunction = typename std::function<void(
unsigned repeat, T contents, BarrierNotifiable *done)>;

/// Typed ReadFunction for primitive types. @return the read value if the
/// read was successful. If the read did not complete, return 0.
template <typename T>
using TypedReadFunction = typename std::function<T (
unsigned repeat, BarrierNotifiable *done)>;

/// Setup the address bounds from a single CDI group declaration.
/// @param group is an instance of a group, for example a segment.
template <class G> void set_bounds_from_group(const G &group)
Expand Down Expand Up @@ -227,6 +236,35 @@ protected:
register_element(entry.offset(), SIZE, read_f, write_f);
}

/// Registers a numeric typed element.
/// @param T is the type argument, e.g. uint8_t.
/// @param entry is the CDI ConfigRepresentation.
/// @param read_f will be called to read this data
/// @param write_f will be called to write this data
template <typename T>
void register_numeric(const NumericConfigEntry<T> &entry,
TypedReadFunction<T> read_f, TypedWriteFunction<T> write_f)
{
expand_bounds_from_group(entry);
auto trf = [read_f](unsigned repeat, string *contents,
BarrierNotifiable *done) {
T result = read_f(repeat, done);
contents->clear();
contents->resize(sizeof(T));
*((T *)&((*contents)[0])) =
NumericConfigEntry<T>::endian_convert(result);
};
auto twf = [write_f](unsigned repeat, string contents,
BarrierNotifiable *done) {
contents.resize(sizeof(T));
T result = NumericConfigEntry<T>::endian_convert(
*(const T *)contents.data());
write_f(repeat, result, done);
};
register_element(
entry.offset(), entry.size(), std::move(trf), std::move(twf));
}

/// Registers a repeated group. Calling this function means that the
/// virtual memory space of the group will be looped onto the first
/// repetition. The correct usage is to register the elements of the first
Expand Down

0 comments on commit d14cf8b

Please sign in to comment.